Skip to content

Instantly share code, notes, and snippets.

@nythrox
Last active January 23, 2023 06:23
Show Gist options
  • Save nythrox/bd958514cb0ff8bf36b5f619a10703bf to your computer and use it in GitHub Desktop.
Save nythrox/bd958514cb0ff8bf36b5f619a10703bf to your computer and use it in GitHub Desktop.
Flutter/Dart do notation
main() {
// Either<dynamic, int> result
final result = Either.Do(() {
final num1 = perform(Right(5));
final num2 = perform(Right(2));
return num1 * num2;
});
print(result.value); // 10
// Option<int> result2
final result2 = Option.Do(() {
final num1 = perform(Some(5));
io(() => print("hi"));
final num2 = perform(Some(2));
return num1 * num2;
}); // hi
print(result2.value); // 10
}
Context context;
Monad<R> Do<R>(Monad<T> Function<T>(T value) of, R Function() action) {
final trace = [];
final ctx = Context(trace);
step() {
final savedContext = context;
ctx.pos = 0;
try {
context = ctx;
return of(action());
} on DoNotationBind catch (bind) {
final pos = context.pos;
return bind.monad.chain((value) {
trace.insert(pos, value);
ctx.pos++;
return step();
});
} on Io catch (exn) {
trace.insert(context.pos, exn.action());
ctx.pos++;
return step() as Future<T>;
} finally {
context = savedContext;
}
}
return step();
}
final do_ = Do;
T perform<T>(Monad<T> monad) {
if (context.pos < context.trace.length) return context.trace[context.pos++];
throw DoNotationBind(monad);
}
class Context {
List<dynamic> trace;
int pos;
Context(this.trace);
}
abstract class Monad<T> {
Monad<T2> map<T2>(T2 Function(T value) mapper);
Monad<T2> chain<T2>(Monad<T2> Function(T value) chainer);
}
class DoNotationBind<T> {
final Monad<T> monad;
DoNotationBind(this.monad);
}
T io<T>(T Function() action) {
if (context.pos < context.trace.length) return context.trace[context.pos++];
throw Io(action);
}
class Io<T> {
final T Function() action;
Io(this.action);
}
class Either<L, R> implements Monad<R> {
final R value;
final L error;
final bool isRight;
final bool isLeft;
const Either._(this.value, this.error, this.isRight, this.isLeft);
static Either<L, R> _left<L, R>(L error) {
return Either<L, R>._(null, error, false, true);
}
static Either<L, R> _right<L, R>(R value) {
return Either<L, R>._(value, null, true, false);
}
static const Left = Either._left;
static const Right = Either._right;
@override
Either<L, R2> chain<R2>(Function(R value) chainer) {
return isRight ? chainer(value) : this;
}
@override
Either<L, R2> map<R2>(R2 Function(R value) mapper) {
return isRight ? Either.Right(mapper(value)) : this;
}
static Either<L, R> Do<L, R>(R Function() action) {
return do_(<T>(value) => Right(value), action);
}
}
const Right = Either.Right;
const Left = Either.Left;
class Option<T> implements Monad<T> {
final T value;
final bool isSome;
const Option._(this.value, this.isSome);
static Option<T> _some<T>(T value) {
return Option<T>._(value, true);
}
static Option<T> _none<T>() {
return Option<T>._(null, false);
}
static const Some = Option._some;
static const None = Option._none;
@override
Option<T2> chain<T2>(Function(T value) chainer) {
return this.isSome ? chainer(value) : this;
}
@override
Option<T2> map<T2>(T2 Function(T value) mapper) {
return this.isSome ? Some(mapper(value)) : this;
}
static Option<T> Do<T>(T Function() action) {
return do_(Some, action);
}
}
const Some = Option.Some;
const None = Option.None;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment