Macros

A lot has changed in Haxe 3

A lot has changed

  • Macros overhauled
  • Full pattern matching supported
  • Abstract types introduced


... too much for this talk

Main questions:

  1. How can I understand all this?
  2. What do I need them for?

Tinkerbell

Haxe on steroids pixiedust.

Haxe Rationale

Keep syntax simple and lean to be newcomer friendly.

Tink Rationale

Use a rich syntax to say more with less.

Why?

Because we can!

Why else?

Because at some point, you stop being a newcomer.
You have made the long term investment of learning Haxe.
You are ready to take in more mind-boggling stuff.

But why this?

Push the language to its limits.
Leave the core lean.

But I like Haxe!

We all do. Tink is optional and granular.

The choice is yours

  • tink_core
  • tink_macros
  • tink_lang
  • tink_reactive
  • tink_tween
  • tink_markup
  • tinx_node
  • tinx_node_mongo
  • tinx_nw
  • ...

So choose

Add functionality granularly.
Experience great integration.

Like a feather

Individual tink libs average at about 1000 lines of code.

A bit of history

  • First released about two years ago
  • Core of a one-size-fits-all Framework
  • Now revised for Haxe 3

At the core ...

... tink is just a Haxe library

  • Outcome
  • Callback
  • Future
  • Surprise
  • Signal

Outcome

All code can fail. Without exception.

Let' formalize that:

enum Outcome<Data, Failure> {
	Success(data:Data);
	Failure(failure:Failure);
}

Classical approach:

try {
	var data = haxe.Json.parse('invalid');
	//do something
}
catch(e:Dynamic) {
	trace('invalid JSON $e);
}

Tink way:

switch haxe.Json.parse('invalid') {
	case Success(data): //do something
	case Failure(f): trace('invalid JSON $e);
}

Outcome


Dealing with outcomes:


using tink.core.types.Outcome;

var outcome = 
	getJson('config.json')
	.map(function (o) return o.color);
	
outcome.sure();//will get the color or fail

var outcome = 
	getJson('config.json')
	.orTry(getJson('altconfig.json'))
	.map(function (o) return o.color);

var color = outcome.orUse(0xFF0000);

Callbacks

We can formalize those as well.

Callback:

abstract Callback<T> from T->Void from Void->Void {
	function invoke(data:T):Void;
}

CallbackLink:

abstract CallbackLink {
	function dissolve():Void;
}

CallbackList:

abstract CallbackList<T> {
	var length(get, never):Int;
	function new():Void;
	function add(cb:Callback<T>):CallbackLink;
	function invoke(data:T):Void;
	function clear():Void;
}

Future

A glimpse of it:

abstract Future<T> {
	function get(cb:Callback<T>):CallbackLink;
	function map<A>(f:T->A):Future<A>;
	function flatMap<A>(f:T->Future<A>):Future<A>;
}

Example:

getJson('config.json')
	.flatMap(
		function (config:{ dataPath:String}) 
			return getJson(dataPath)
	).get(
		function (data) trace(data)
	);

Roll your own:

class FutureOp<T> {
	function new():Void;
	function asFuture():Future<T> return future;
	function invoke(result:T):Bool;
}

Surprise

Becomes straight forward to express:

typedef Surprise<D,F> = Future<Outcome<D, F>>;

Signal

Signal:

abstract Signal<T> {
	function watch(cb:Callback<T>):CallbackLink;
	function next():Future<T>
	function map<A>(f:T->A):Signal<A>;
	function dike():Signal<T>;
}

Noise:

enum Noise { Noise; }

And then came macros

And tink_macros tried making them bearable ...

... but that's history :)

A new language

Meet tink_lang

Usage

class MyClass implements tink.lang.Cls { ... }

Sugar everywhere!

At Expression Level

  • loop magic
  • trailing callbacks
  • short lambdas
  • short bind
  • watching
  • pipelining
  • escaping

Sugar everywhere!

At Class Level

  • initialization
  • forwarding
  • properties
  • signals & futures
  • partial implementations

Loop magic

//abitrary steps:
for (i += .2 in 0...10) {}
//backward:
for (i -= 1 in 100...0) {}
//key-value-pair iteration:
for (key => val in someMap) {}
//parallel looping:
for ([foo in foos, bar in bars]) {}
//with defaults:
for ([foo in foos || genericFoo, bar in bars]) {}

Trailing callbacks

someAsyncCall(p1, p2) => handler
-->  someAsyncCall(p1, p2, handler)

Short lambdas

With return:

[x] => x + 1
-->  function (x) return x + 1

@f(x) if (x > 0) x else -x
-->  function (x) return if (x > 0) x else -x;

Without it:

![x] => foo = x
-->  function (x) foo = x;

@do(x) => x.doSomething()
-->  function (x) x.doSomething()

Put together

myButton.on('click') => @do {
	step1;
	step2;
	step3;
}

Short bind

Pretty simple:

mc.addChildAt(_, 0);
-->  mc.addChildAt.bind(_, 0);

Watching

Do it @when:

@when(someSignal) handler;
@when(someFuture) handler;
@when(someSignal.next()) handler;

Example:

@when(myButton.clicked) @do trace('clicked');

Work with old events:

@when(myButton['click']) @do trace('clicked');

Do it @until:

@when(myButton['mousedown']) @do 
	@until(stage['mouseup'])
		@when(stage['mousemove']) @do trace('drag')

Do it @with:

@with(myConfirm) {
	@when(canceled) @do trace('canceled'); 
	@when(confirmed) @do trace('confirmed'); 
}

Pipelining

Because asynchronous stuff is at least as tedious, as it is cool.

It would be nice to make it more manageable.

The basic idea:

{
	result1: operation1,
	result2: operation2,
	result3: operation3,
} => {
	result4: operation4(result1, result2);
} => doSomething(result3, result4, result1);
The beauty of it: whether or not it is asynchronous doesn't matter

Too much sugar?

Syntax sugars can sometimes be in the way

Go diet:

@:diet for (key => val in map) {}

Initializing

class Foo implements tink.lang.Cls {
	var a = [3, 4, 5];
	var b:Int = _;
}

Forwarding

class Stack<T> implements tink.lang.Cls {
	@:forward(push, pop, length, iterator) var data:Array<T> = [];	
}

Properties

class Foo implements tink.lang.Cls {
	@:prop var a = [3, 4, 5];
	var b:Int = _;
}

Futures and Signals

class Foo implements tink.lang.Cls {
	@:signal var onSay:String;
	@:signal var onShrug;
	@:signal var onEcho:String = 
		this.onSay.map(
			[s] => [for (i in 0...3) 
				s.toLowerCase()
			].join(' ')
		)
	@:future var died:Outcome<Heaven, Hell>;
}

Partial implemententations

interface Enumerable<T> implements tink.lang.Cls {
	function forEach(f:T->Void):Void;
	function filter(f:T->Bool):Array<T> {
		var ret = [];
		forEach(function (elt:T)
			if (f(elt)) ret.push(elt)
		);
	}
}

Conclusion