sealed class Option { const Option(); bool get isSome => this is Some; bool get isNone => this is None; factory Option.some(T value) { return Some(value); } factory Option.none() { return const None(); } factory Option.from(T? value) { if (value == null) { return const None(); } return Some(value); } T? unwrapOrNull() { if (this is Some) { return (this as Some).value; } return null; } T unwrap() { if (this is Some) { return (this as Some).value; } throw StateError('Cannot unwrap None'); } @override bool operator ==(Object other) { if (identical(this, other)) return true; if (other is! Option) return false; if (this is None && other is None) { return true; } return this is Some && other is Some && this.unwrap() == other.unwrap(); } @override int get hashCode { if (this is Some) { return (this.unwrap()).hashCode; } return 0; } @override String toString() { if (this is Some) { return 'Some(${this.unwrap()})'; } return 'None'; } } class Some extends Option { final T value; const Some(this.value); } class None extends Option { const None(); } // Implemented as an extension rather than adding to the Option class because // the type argument for None is not known at compile time, and fallback to Never // As such, when the method is called on Option, with a default value of T, // a runtime error extension OptionExtensions on Option { T unwrapOr(T defaultValue) { if (this is Some) { return (this as Some).value; } return defaultValue; } }