Maybe I'm missing something, but I don't see how that's special to option types. Here's a set of extension methods in Dart that provide the operations you describe:
extension NullableExtensions<T> on T? {
R? map<R>(R Function(T) transform) => this == null ? null : transform(this);
T? filter(bool Function(T) predicate) {
if (this == null) return null;
if (predicate(this)) return this;
return null;
}
}
extension FunctionExtensions<T, R> on R Function(T) {
R? Function(T?) lift() => (T? param) => param == null ? null : this(param);
}
extension NullableIterableExtensions<T> on Iterable<T?> {
Iterable<R?> map<R>(R Function(T) transform) =>
this.map((e) => e == null ? null : transform(e));
Iterable<T?> filter(bool Function(T) predicate) =>
where((e) => e == null || predicate(e));
}
Chaining has built-in syntax: int? maybeInt = ...
print(maybeInt?.isEven.toString());
I'm not sure what you mean by "compose".Of course, this is not entirely as expressive as option types because of the inability to nest, but the article is pretty clear that nesting is the major advantage of option types.