Last active
March 24, 2022 14:45
-
-
Save eernstg/45ec3839bf702fd5de4b4e28271635c4 to your computer and use it in GitHub Desktop.
Typeclass example
This file contains hidden or 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
/* | |
Several examples, separated by '-----' lines. | |
We probably want to use a regular type variable declaration list plus | |
a constraint specification list (to request a dispatcher object for a | |
specific typeclass instance), so I'm using `where` clauses in various | |
headers. | |
Dispatcher objects are holders of functions, and there is no particular | |
notion of a receiver, so we're likely to benefit _greatly_ from sound | |
variance. So I used `in/out/inout` everywhere. | |
Invocations of typeclass methods use the name. So if `g` is a typeclass | |
getter for a typeclass `TC` and `tcDispatcher` is a dispatcher for `TC` | |
which is in scope, then we may invoke `g` as `g`, and it is desugared to | |
`tcDispatcher.g`. We might want to use a keyword which stands for the | |
dispatcher object (say `dis`, in line with `this` ;-). | |
As proposed here, we're adding typeclass methods to the lexical scope in | |
the same way as we're adding instance members that are not in the lexical | |
scope, but it may be bad for the readability of the code that we introduce | |
a bunch of plain names into the scope based on a `where` clause that may | |
be many lines away. | |
*/ | |
// ---------------------------------------------------------------------- | |
// Basic example. | |
/* | |
typeclass Plus<inout X, in Y> { | |
X plus(X x, Y y); | |
} | |
instance Plus<int, int> { | |
int plus(int x, int y) => x + y; | |
} | |
instance Plus<double, double> { | |
double plus(double x, double y) => x + y; | |
} | |
instance Plus<int, double> { | |
int plus(int x, double y) => (x.toDouble() + y).round(); | |
} | |
instance Plus<double, int> { | |
double plus(double x, int y) => x + y.round(); | |
} | |
instance<inout X, in Y> Plus<Iterable<X>, Iterable<Y>> where Plus<X, Y> { | |
Iterable<X> plus(Iterable<X> xs, Iterable<Y> ys) { | |
Never fail() => throw StateError("Plus: Lists of different length"); | |
Iterator<Y> it = ys.iterator; | |
List<X> result = []; | |
for (var x in xs) { | |
if (!it.moveNext()) fail(); | |
result.add(plus(x, it.current)); | |
} | |
if (it.moveNext()) fail(); | |
} | |
} | |
void callPlus<X, Y>(X x, Y y) where Plus<X, Y> { | |
print(plus(x, y)); | |
} | |
void callPlusList<X, Y>(X x, Y y) where Plus<X, Y> { | |
print(plus([x], [y])); | |
} | |
void main() { | |
print(plus(1, 1)); // '2'. | |
print(plus(1.0, 1.0)); // '2.0'. | |
print(plus(1, 1.0)); // '2'. | |
print(plus(1.0, 1)); // '2.0'. | |
callPlus(1, 1); // '2'. | |
callPlus(1.0, 1.0); // '2.0'. | |
callPlus(1, 1.0); // '2'. | |
callPlus(1.0, 1); // '2.0'. | |
print(plus([1], [1.0])); // '[2]'. | |
callPlus([1.0, 2.0], [2, 1]); // '[3.0, 3.0]'. | |
callPlusList([1.0, 2.0], [2, 1]); // '[[3.0, 3.0]]'. | |
} | |
*/ | |
// typeclass Plus<inout X, in Y> { | |
// X plus(X x, Y y); | |
// } | |
abstract class PlusDispatcher<inout X, in Y> { | |
const PlusDispatcher(); | |
X plus(X x, Y y); | |
} | |
// instance Plus<int, int> { | |
// int plus(int x, int y) => x + y; | |
// } | |
class PlusIntIntDispatcher implements PlusDispatcher<int, int> { | |
const PlusIntIntDispatcher(); | |
int plus(int x, int y) => x + y; | |
} | |
// instance Plus<double, double> { | |
// double plus(double x, double y) => x + y; | |
// } | |
class PlusDoubleDoubleDispatcher implements PlusDispatcher<double, double> { | |
const PlusDoubleDoubleDispatcher(); | |
double plus(double x, double y) => x + y; | |
} | |
// instance Plus<int, double> { | |
// int plus(int x, double y) => (x.toDouble() + y).round(); | |
// } | |
class PlusIntDoubleDispatcher implements PlusDispatcher<int, double> { | |
const PlusIntDoubleDispatcher(); | |
int plus(int x, double y) => (x.toDouble() + y).round(); | |
} | |
// instance Plus<double, int> { | |
// double plus(double x, int y) => x + y.round(); | |
// } | |
class PlusDoubleIntDispatcher implements PlusDispatcher<double, int> { | |
const PlusDoubleIntDispatcher(); | |
double plus(double x, int y) => x + y.round(); | |
} | |
// instance<inout X, in Y> Plus<Iterable<X>, Iterable<Y>> where Plus<X, Y> { | |
// Iterable<X> plus(Iterable<X> xs, Iterable<Y> ys) { | |
// Never fail() => throw StateError("Plus: Lists of different length"); | |
// Iterator<Y> it = ys.iterator; | |
// List<X> result = []; | |
// for (var x in xs) { | |
// if (!it.moveNext()) fail(); | |
// result.add(plus(x, it.current)); | |
// } | |
// if (it.moveNext()) fail(); | |
// } | |
// } | |
class PlusIterableIterableDispatcher<inout X, in Y> | |
implements PlusDispatcher<Iterable<X>, Iterable<Y>> { | |
final PlusDispatcher<X, Y> dispatcher; | |
const PlusIterableIterableDispatcher(this.dispatcher); | |
Iterable<X> plus(Iterable<X> xs, Iterable<Y> ys) { | |
Never fail() => throw StateError("Plus: Lists of different length"); | |
Iterator<Y> it = ys.iterator; | |
List<X> result = []; | |
for (var x in xs) { | |
if (!it.moveNext()) fail(); | |
result.add(dispatcher.plus(x, it.current as Y)); | |
} | |
if (it.moveNext()) fail(); | |
return result; | |
} | |
} | |
// void callPlus<X, Y>(X x, Y y) where Plus<X, Y> { | |
// print(plus(x, y)); | |
// } | |
void callPlus<X, Y>( | |
PlusDispatcher<X, Y> dispatcher, X x, Y y) { | |
print(dispatcher.plus(x, y)); | |
} | |
// void callPlusList<X, Y>(X x, Y y) where Plus<X, Y> { | |
// print(plus([x], [y])); | |
// } | |
void callPlusList<X, Y>( | |
PlusDispatcher<X, Y> dispatcher, X x, Y y) { | |
print(PlusIterableIterableDispatcher(dispatcher).plus([x], [y])); | |
} | |
void main() { | |
// print(plus(1, 1)); | |
print(const PlusIntIntDispatcher().plus(1, 1)); | |
// print(plus(1.0, 1.0)); | |
print(const PlusDoubleDoubleDispatcher().plus(1.0, 1.0)); | |
// print(plus(1, 1.0)); | |
print(const PlusIntDoubleDispatcher().plus(1, 1.0)); | |
// print(plus(1.0, 1)); | |
print(const PlusDoubleIntDispatcher().plus(1.0, 1)); | |
// callPlus(1, 1); | |
callPlus(const PlusIntIntDispatcher(), 1, 1); | |
// callPlus(1.0, 1.0); | |
callPlus(const PlusDoubleDoubleDispatcher(), 1.0, 1.0); | |
// callPlus(1, 1.0); | |
callPlus(const PlusIntDoubleDispatcher(), 1, 1.0); | |
// callPlus(1.0, 1); | |
callPlus(const PlusDoubleIntDispatcher(), 1.0, 1); | |
// print(plus([1], [1.0])); | |
print(const PlusIterableIterableDispatcher(const PlusIntDoubleDispatcher()) | |
.plus([1], [1.0])); | |
// callPlus([1.0, 2.0], [2, 1]); | |
callPlus( | |
const PlusIterableIterableDispatcher(const PlusDoubleIntDispatcher()), | |
[1.0, 2.0], | |
[2, 1], | |
); | |
// callPlusList([1.0, 2.0], [2, 1]); | |
callPlusList( | |
const PlusIterableIterableDispatcher(const PlusDoubleIntDispatcher()), | |
[1.0, 2.0], | |
[2, 1], | |
); | |
} | |
// ---------------------------------------------------------------------- | |
// Same type variable, multiple type classes. | |
// typeclass A<in X, out Y> { | |
// Y m(X x); | |
// } | |
abstract class ADispatcher<in X, out Y> { | |
const ADispatcher(); | |
Y m(X x); | |
} | |
// typeclass B<out X> { | |
// X get g; | |
// } | |
abstract class BDispatcher<out X> { | |
const BDispatcher(); | |
X get g; | |
} | |
// instance<inout X> A<X, X> { | |
// X m(X x) => x; | |
// } | |
class AXXDispatcher<inout X> implements ADispatcher<X, X> { | |
const AXXDispatcher(); | |
X m(X x) => x; | |
} | |
// instance B<int> { | |
// int get g => 1; | |
// } | |
class BIntDispatcher implements BDispatcher<int> { | |
const BIntDispatcher(); | |
int get g => 1; | |
} | |
// X foo<X extends num, Y>() where A<X, Y>, B<X> { | |
// return m(g); | |
// } | |
Y foo<X extends num, Y>( | |
ADispatcher<X, Y> aDispatcher, BDispatcher<X> bDispatcher) { | |
print('X == $X, Y == $Y'); | |
return aDispatcher.m(bDispatcher.g); | |
} | |
void main() { | |
// num n = foo(); | |
num n = foo(const AXXDispatcher(), const BIntDispatcher()); | |
// print(n); | |
print(n); | |
} | |
// ---------------------------------------------------------------------- | |
// Dispatchers in multiple scopes. | |
// typeclass A<in X, out Y> { | |
// Y m(X x); | |
// } | |
abstract class ADispatcher<in X, out Y> { | |
const ADispatcher(); | |
Y m(X x1, X x2); | |
} | |
// typeclass B<out X> { | |
// X get g; | |
// } | |
abstract class BDispatcher<out X> { | |
const BDispatcher(); | |
X get g; | |
} | |
// instance<inout X> A<X, X> { | |
// X m(X x1, X x2) => x2; | |
// } | |
class AXXDispatcher<inout X> implements ADispatcher<X, X> { | |
const AXXDispatcher(); | |
X m(X x1, X x2) => x2; | |
} | |
// instance B<int> { | |
// int get g => 1; | |
// } | |
class BIntDispatcher implements BDispatcher<int> { | |
const BIntDispatcher(); | |
int get g => 1; | |
} | |
// class C<inout X, out Y> where A<X, Y> { | |
// X x; | |
// C(this.x); | |
// Y foo<Z extends X>() where B<Z> { | |
// return m(x, g); | |
// } | |
// } | |
class C<inout X, out Y> { | |
final ADispatcher<X, Y> aDispatcher; | |
X x; | |
C(this.aDispatcher, this.x); | |
Y foo<Z extends X>(BDispatcher<Z> bDispatcher) { | |
print('X == $X, Y == $Y, Z == $Z'); | |
return aDispatcher.m(x, bDispatcher.g); | |
} | |
} | |
void main() { | |
// var c = C(1.5); | |
var c = C(const AXXDispatcher(), 1.5); // NB: <dynamic, dynamic>. | |
// print(c.foo()); | |
print(c.foo(const BIntDispatcher())); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment