-
-
Save jdonaldson/5891600 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package ; | |
using TestX.Eqs; | |
// consider the following interfaces | |
interface Compare<T> { | |
public function compare(b:T):Int; | |
} | |
interface Eq<T> { | |
public function eq(b:T):Bool; | |
} | |
// in the oop world you have to implement these 2 interface | |
// when you define your own type to make it compatible with these interfaces. | |
// This would look like this: | |
class MyClass implements Compare<MyClass> implements Eq<MyClass> { | |
public var x : Int; | |
public function new (x:Int) { | |
this.x = x; | |
} | |
public function eq(b:MyClass):Bool { | |
return this.x == b.x; | |
} | |
public function compare(b:MyClass):Int { | |
return this.x < b.x ? -1 : this.x > b.x ? 1 : 0; | |
} | |
} | |
// The implementation has to be done when you define your type, which means you | |
// have know at this point which interfaces you need. But what do you do if you need | |
// to implement another interface later? | |
// If it's your own type it's not a big deal, but how about making already existing types | |
// (types from other libraries) instances of Compare or Eq? | |
// This is not possible without some kind of wrapper around it | |
// (but the wrapper doesn't preserve the type). | |
// A workaround in haxe would be to define Compare and Eq | |
// as typedefs, but in that case already exisiting types must be shaped exactly | |
// like the typedef, which means functions must have the same name and signature. | |
// Here is the typeclass approach. Type classes are defined like interfaces in OOP, | |
// but they don't have a this type, they expect both types as | |
// parameters. | |
// In this example i use the suffix X for type class implementations | |
// to distinguish them from the OOP example. | |
interface CompareX<T> { | |
public function compare(a:T, b:T):Int; | |
} | |
interface EqX<T> { | |
public function eq(a:T, b:T):Bool; | |
} | |
class MyClassX { | |
public var x:Int; | |
public function new (x:Int) { | |
this.x = x; | |
} | |
} | |
// the implementations of type classes (think Interfaces) are decoupled of the type itself. | |
// so it's not a big deal to implement them for already exisiting types. | |
class MyClassXCompare implements CompareX<MyClassX> { | |
public function new () {} | |
public function compare(a:MyClassX, b:MyClassX):Int { | |
return a.x < b.x ? -1 : a.x > b.x ? 1 : 0; | |
} | |
} | |
class MyClassXEq implements EqX<MyClassX> { | |
public function new () {} | |
public function eq(a:MyClassX, b:MyClassX):Bool { | |
return a.x == b.x; | |
} | |
} | |
// for all typeclass functions in haxe it is usefull to have a static function with the same | |
// functionality, it's important to note that the typeclass itself becomes a function parameter. | |
class Eqs { | |
public static inline function eq<T>(a:T, b:T, eq:EqX<T>):Bool { | |
return eq.eq(a,b); | |
} | |
} | |
class Compares { | |
public static inline function compare<T>(a:T, b:T, c:CompareX<T>):Int { | |
return c.compare(a,b); | |
} | |
} | |
class TestX { | |
public static function main () { | |
// usage oop | |
new MyClass(1).eq(new MyClass(1)); | |
// usage typeclass | |
Eqs.eq(new MyClassX(1), new MyClassX(2), new MyClassXEq()); | |
// with using it becomes | |
new MyClassX(1).eq(new MyClassX(2), new MyClassXEq()); | |
// so at this point you have to pass the instance of Eq explicit at call time | |
// which is nice because it decouples the data of the type from its methods. | |
// But it's also verbose, because you have to pass an instance of the | |
// interface/typeclass explicit. | |
// The Haskell compiler for example injects the type class at this point, so that | |
Eqs.eq(new MyClassX(1), new MyClassX(2)); | |
// becomes: | |
Eqs.eq(new MyClassX(1), new MyClassX(2), new MyClassXEq()); | |
// based on the type information of the parameters. | |
// In some way that's also what scuts does. It converts the call ("_" is no special | |
// syntax it's just a macro function) | |
Eqs.eq._(new MyClassX(1), new MyClassX(2)); | |
// or | |
new MyClassX(1).eq._(new MyClassX(2)); | |
// into something like this | |
Eqs.eq(new MyClassX(1), new MyClassX(2), new MyClassEqX())); | |
// it doesn't really use new at this point, it injects an instance of MyClassEqX | |
// from the context, this instance can be a static variable, a function or whatever. | |
// the macro "_" searches the context for an expression with the desired type. | |
// e.g. the expression Eqs.eq.bind(1,1,_) has needs an expression of type EqX<Int> | |
// as the last parameter. | |
// Because of it's decoupled structure, type classes are composable. | |
// You can use them on nested data types (check the ArrayEq instance below) | |
var a = [new MyClassX(1)]; | |
var b = [new MyClassX(1)]; | |
// compare arrays with MyClassX instances | |
trace(a.eq(b, new ArrayEq(new MyClassEq()))); | |
// with type class injection in scuts it becomes | |
// (the required typeclass instances must be in scope) | |
trace(a.eq._(b); | |
} | |
} | |
class ArrayEq<T> implements EqX<Array<T>> { | |
// we need to know how we can compare the elements of the array | |
var eqT:EqX<T>; | |
public function new (eqT:EqX<T>) { | |
this.eqT = eqT; | |
} | |
public function eq(a:Array<T>, b:Array<T>):Bool { | |
if (a.length != b.length) return false; | |
for (i in 0...a.length) { | |
var e1 = a[i]; | |
var e2 = b[i]; | |
if (!eqT.eq(e1, e2)) return false; | |
} | |
return true; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment