Skip to content

Instantly share code, notes, and snippets.

@eernstg
Last active March 24, 2022 14:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save eernstg/45ec3839bf702fd5de4b4e28271635c4 to your computer and use it in GitHub Desktop.
Save eernstg/45ec3839bf702fd5de4b4e28271635c4 to your computer and use it in GitHub Desktop.
Typeclass example
/*
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