mixin 0.9.5

Macro-powered mixins for haxe

Released 2017-11-10.

To install, run:

haxelib install mixin 0.9.5

See using Haxelib in Haxelib documentation for more information.

Maintainermodjke
Websitehttps://github.com/modjke/mixin
Current version0.9.5
StatisticsInstalled 12 times
LicenseMIT
Tags

README.md

# Mixins for haxe (lib.haxe.org/p/mixin) Macro-powered mixin system for haxe 3.4
haxe 4.0 will be supported after a stable release
PRs/bug reports/feature requests are welcomed

Build Status

Features

  • Mixin is an interface with implementation
  • Can include (add) it's properties and methods into any class
  • Can overwrite base class methods and constructors
  • Can declare base class requirements
  • Encourages code reuse
  • Inheritance (you) vs composition (that guy she tells you not to worry about)
  • Made with top-notch compile-time macros, no runtime overhead

Installation

  • Haxelib version: haxelib install mixin and -lib mixin
  • Git version: clone into your project and add to your hxml -cp mixin/lib and mixin/extraParams.hxml

How to use

  1. Declare your mixin as interface with @mixin meta
  2. Inlcude mixins into your class by adding implements Mixin

Obligatory logger example

@mixin interface Logger {
	var loggingEnabled:Bool = true;
	public function log(message:String):Void {
		if (loggingEnabled)
			trace(message);
	}
}

class A implements Logger {
	public function new() {
		log("called A constructor");
	}
}

class B implements Logger {
	public function new() {
		loggingEnabled = false;
		log("called B constructor");
	}
}

Logger mixin adds private var loggingEnabled and public function log to every base class without need to extend it. Also mixin's public field will become interface fields so casting to mixin and calling them is possible:

var logger:Logger = new A(); 
logger.log("Hey");

Implementation details

Call base class method within mixin and vice versa
@mixin interface Mixin {
	public function callBase():Void {
		base();
	}

	function mixin():Void {
		trace("mixin() called");
	}
}

class Object implements Mixin {
	public function callMixin() {
		this.mixin();
	}

	function base() {
		trace("base() called");
	}
}

Since including above mixin inside a class that have no base() method will result in compile-time error it is possible to require base class to have that method, keep reading :)

Base class requirements
  • @base field meta to require base class to have that field implemented (private or public) (no function body required)
  • @baseExtends(superClass) mixin meta to make sure base class extends superClass
  • @baseImplements(interface1, interface2, ...) mixin meta to make sure base class implements certain interfaces
// base should extend flash.display.DisplayObject
@baseExtends(flash.display.DisplayObject)
// base should implement flash.display.IBitmapDrawable
@baseImplements(flash.display.IBitmapDrawable)
@mixin interface Mixin
{
	//also base should have this function
	@base public function getBitmapData():BitmapData;
}
Overwriting base methods
  • Overwrite any base class method by adding @overwrite([ignoreBaseCalls = false], [inlineBase = false], [addIfAbsent = false]) meta
  • To call overwritten base method use $base.method()
  • Overwriting non-existent base method will trigger an error by default, addIfAbsent=true will add non-existent base method with all super calls in place if needed
  • Calling $base method is mandatory, ignoreBaseCalls=true to make it optional
  • Multiple mixins can overwrite the same method, if one of them is not calling base method behaviour is undefined
  • Base method can be inlined by setting inlineBase=true (super calls and multiple returns are not supported)
class Object implements Mixin {
	public function foo(arg:Int):Void {
		//do something
	}
}
@mixin interface Mixin {
	@overwrite public function foo(arg:Int):Void {
		//do smth
		$base.foo(arg);		//call base class method
		//do more
	}
}
Overwriting getters / setters
  • Overwriting getters and setters is similar to overwriting methods
  • Overwriting getter or setter for @:isVar field is not allowed
class Object implements VeryNastyMixin {
	var foo(default, set):Int;
	function set_foo(v:Int):Int {
		return foo = v;
	}
}
@mixin interface VeryNastyMixin {
	// declaring base property is optional
	@base var foo(default, set):Int;
	
	@overwrite function set_foo(v:Int):Int {   
		v = Std.int(Math.random() * 1000);
		return $base.set_foo(v);
	}
}
Overwriting constructor
  • Constructor can be overwritten to perform some mixin initialization
  • Similar to overwriting methods but base constructor can be called only once as $base()
  • Overwriting constructors with return statements is not supported
class Object implements Mixin {
	public function new() {
		trace(mixinVar);	// traces "initialized"
	}
}
@mixin interface Mixin {
	var mixinVar:String;
	@overwrite public function new() {
		mixinVar = "initialized";
		$base();
	}
}
Merging (extending) mixins
  • Mixins can extend other mixins (that merges them together)
  • Interfaces can extend mixins (to alias certain collection of mixins)
  • One mixin can be included in the hierarchy only once
@mixin interface Human {}
@mixin interface Handsome {}
@mixin interface Talanted {}
@mixin interface Actor extends Human extends Talanted extends Handsome {}
@mixin interface KeanuReeves extends Actor {}
@mixin interface Driver {}
@mixin interface Killer {}
class JohnWick implements KeanuReeves implements Driver implements Killer {}
Typed mixins
  • Typed mixins are very cool! :)
  • Typed methods (function\<T\>) and constraints on type parameters are supported too.
class Object implements Collection<String> {
	function createItem():String return "Item!";
	
	public function new() {
		createCollection(100);
	}
}
@mixin interface Collection<T> {    
	@base function createItem():T;

	var collection:Array<T>;
	public function createCollection(count:Int) {
		collection = [for (i in 0...count) createItem()];
	}
}

Limitations

  • All mixin fields should be explicitly typed (same applies to base class fields if they declared as @base or @overwrite in a mixin)
  • All @base & @overwrite methods should have the same arg names/arg types/arg defaults/return type as a base method
  • Using using for mixin module is not supported
  • Importing static functions is not supported
  • import.hx is not yet supported (that said everything would work if you have the same import.hx for mixin and base class module, but in that case your mixin will depend on import.hx location)

Lincese

Copyright (c) 2017 Ignatiev Mikhail (https://github.com/modjke)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.