Skip to content

Instantly share code, notes, and snippets.

@eernstg
Last active September 10, 2025 08:19
Show Gist options
  • Select an option

  • Save eernstg/27aa5023d6bfbc1a68cd1ef78daf970a to your computer and use it in GitHub Desktop.

Select an option

Save eernstg/27aa5023d6bfbc1a68cd1ef78daf970a to your computer and use it in GitHub Desktop.
Emulate the monoid example from the talk by Brian Goetz about Java type classes (JVMLS 2025)
// This library shows how metaobjects in Dart can be used to achieve a
// similar effect as the Java type class proposal in the talk by Brian
// Goetz. The point is that we can use types (no need to have an instance)
// to work with "static-like" properties like the `zero` value and the
// `plus` operation of a `Monoid`.
//
// No attempt is made to include the syntactic sugar than allows
// `witness.plus(a, b)` to be written as `a + b`. That's a separate
// consideration, and it might be useful with metaobjects as well as
// in other contexts. So that's a topic for another day, and the code
// below simply uses the syntax `witness.plus(a, b)`.
//
// So what does this example achieve? It shows that it is possible to write
// generic code like `computeSum` where the type argument is used to get
// access to an instance of `Monoid<X>` which has the defining elements of
// the monoid (that is, `zero` and `plus`), which allows us to perform
// computations in the monoid without depending on which monoid it is.
// As examples, we're using this to add two strings in the first invocation
// of `computeSum` in `main`, and add two `Optional<String>` instances in
// the second invocation.
//
// The underlying mechanism is the `metaobjects` proposal, see
// https://github.com/dart-lang/language/blob/main/working/4200-metaobjects/feature-specification.md,
// which is used to obtain a "metaobject" from the given type `X`, which
// is then also an instance of `Monoid<X>`. The crucial point is that
// we're able to obtain a `Monoid<Y>` based on the type `Optional<Y>`,
// which means that we have to decompose the type `Optional<Y>` and
// get access to the type argument `Y`, and use that to obtain a
// `Monoid<Y>`, and then use that to build a `Monoid<Optional<Y>>`.
// This is not possible in current Dart, but it is at the core of the
// `metaobjects` proposal.
//
// One thing should be noted: This example does _not_ assume that we will
// be able to change the `String` class (to give it a suitable clause of
// the form `static implements T` where `T` is a subtype of `Monoid<String>`).
// On the contrary, it demonstrates that we can handle a set of "well-known"
// types (including `String` and other widely used types), and we can handle
// combinations of those well-known types and other types (and the other
// types do have the `static implements` clause such that they can be used
// directly based on metaobjects). The support for "well-known" types causes
// the types to be less safe (because we can't just require that the types
// and type arguments always satisfy constraints like
// `static extends Monoidable<T>` for any particular `T`). So that's a trade-off:
// `getMonoid` can return null because of this; if we had not supported `String`
// and its well-known brethren then the types could have been fully checked
// statically.
// ------------------------------ Optional.
sealed class Optional<X> static extends OptionalMonoidBuilder<X> {
const Optional();
const factory Optional.some(X x) = Some;
const factory Optional.none() = None;
}
final class Some<X> extends Optional<X> {
final X value;
const Some(this.value);
String toString() => 'Some($value)';
}
final class None extends Optional<Never> {
const None();
String toString() => "None";
}
class OptionalMonoidBuilder<X> extends MonoidBuilder<Optional<X>, X> {
Monoid<Optional<X>> monoid(Monoid<X> subMonoid) => MonoidImpl<Optional<X>>(
Optional.some(subMonoid.zero),
(Optional<X> a, Optional<X> b) => switch ((a, b)) {
(None(), _) || (_, None()) => const None(),
(Some<X>(value: final aa), Some<X>(value: final bb)) =>
Optional.some(subMonoid.plus(aa, bb)),
},
);
}
// ------------------------------ Monoid.
sealed class Monoidable<X> {}
abstract class Monoid<X> implements Monoidable<X> {
X get zero;
X plus(X a, X b);
}
class MonoidImpl<X> implements Monoid<X> {
final X zero;
final X Function(X, X) _plus;
const MonoidImpl(this.zero, this._plus);
X plus(X a, X b) => _plus(a, b);
}
abstract class MonoidBuilder<X, Y /*static extends Monoidable<Y>*/>
implements Monoidable<X> {
Monoid<X> monoid(Monoid<Y> y);
Monoid<Y>? get subMonoid => getMonoid<Y>();
}
Monoid<X>? getMonoid<X /*static extends Monoidable<X>*/>() {
// Well-known types.
if (X == String) {
return stringMonoid as Monoid<X>;
} else if (<X>[] is List<Iterable<Object?>>) {
// ... somehow extract the type argument.
} else {
// ... whatever else is "well-known".
}
// Types with built-in support.
final meta = X();
if (meta is Monoid<X>) return meta;
if (meta is MonoidBuilder<X, Object?>) {
final meta2 = meta.subMonoid;
return meta2 == null ? null : meta.monoid(meta2);
}
return null;
}
// ------------------------------ "Well-known" types.
String _stringPlus(String a, String b) => '$a$b';
const Monoid<String> stringMonoid = MonoidImpl("", _stringPlus);
// ------------------------------ Example.
X computeSum<X static extends Monoidable<X>>(X a, X b) {
final monoid = getMonoid<X>()!;
return monoid.plus(a, b);
}
void main() {
final x = computeSum("Hello, ", "world!");
print(x); // 'Hello, world!'.
final y = computeSum(
Optional.some("Hello, "),
Optional.some("world!"),
);
print(y); // 'Some(Hello, world!)'.
}
// Same example as `monoid.dart`, but modified such that it can be
// executed (`monoid.dart` cannot run because it relies on the metaobjects
// feature, which hasn't been added to the language yet).
// ------------------------------ Emulate metaobject.
// Can't do the general case, just cover the cases we actually have.
Monoidable<X> emulateMeta<X>() {
switch (X) {
case const (String):
return stringMonoid as Monoid<X>;
case const (Optional<String>):
return OptionalMonoidBuilder<String>() as Monoidable<X>;
default:
throw ArgumentError("Unsupported case: $X");
}
}
// ------------------------------ Optional.
sealed class Optional<X> /*static extends OptionalMonoidBuilder<X>*/ {
const Optional();
const factory Optional.some(X x) = Some;
const factory Optional.none() = None;
}
final class Some<X> extends Optional<X> {
final X value;
const Some(this.value);
String toString() => 'Some($value)';
}
final class None extends Optional<Never> {
const None();
String toString() => "None";
}
class OptionalMonoidBuilder<X> extends MonoidBuilder<Optional<X>, X> {
Monoid<Optional<X>> monoid(Monoid<X> subMonoid) => MonoidImpl<Optional<X>>(
Optional.some(subMonoid.zero),
(Optional<X> a, Optional<X> b) => switch ((a, b)) {
(None(), _) || (_, None()) => const None(),
(Some<X>(value: final aa), Some<X>(value: final bb)) =>
Optional.some(subMonoid.plus(aa, bb)),
},
);
}
// ------------------------------ Monoid.
sealed class Monoidable<X> {}
abstract class Monoid<X> implements Monoidable<X> {
X get zero;
X plus(X a, X b);
}
class MonoidImpl<X> implements Monoid<X> {
final X zero;
final X Function(X, X) _plus;
const MonoidImpl(this.zero, this._plus);
X plus(X a, X b) => _plus(a, b);
}
abstract class MonoidBuilder<X, Y /*static extends Monoidable<Y>*/>
implements Monoidable<X> {
Monoid<X> monoid(Monoid<Y> y);
Monoid<Y>? get subMonoid => getMonoid<Y>();
}
Monoid<X>? getMonoid<X /*static extends Monoidable<X>*/>() {
// Well-known types.
if (X == String) {
return stringMonoid as Monoid<X>;
} else if (<X>[] is List<Iterable<Object?>>) {
// ... import `dart_internal` and extract the type argument.
} else {
// ... whatever else is "well-known".
}
// Types with built-in support.
final meta = /*X()*/ emulateMeta<X>();
if (meta is Monoid<X>) return meta;
if (meta is MonoidBuilder<X, Object?>) {
final meta2 = meta.subMonoid;
return meta2 == null ? null : meta.monoid(meta2);
}
return null;
}
// ------------------------------ "Well-known" types.
String _stringPlus(String a, String b) => '$a$b';
const Monoid<String> stringMonoid = MonoidImpl("", _stringPlus);
// ------------------------------ Example.
X computeSum<X /*static extends Monoidable<X>*/>(X a, X b) {
final monoid = getMonoid<X>()!;
return monoid.plus(a, b);
}
void main() {
final x = computeSum("Hello, ", "world!");
print(x); // 'Hello, world!'.
final y = computeSum(
Optional.some("Hello, "),
Optional.some("world!"),
);
print(y); // 'Some(Hello, world!)'.
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment