Last active
August 2, 2023 16:09
-
-
Save CoderNamedHendrick/ff14e4f221cb5290ddc219dc45f940d7 to your computer and use it in GitHub Desktop.
Implementing Options in dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'dart:async'; | |
typedef Lazy<T> = T Function(); | |
/// Represents a value of one of two possible types. | |
/// Instances of [Either] are either an instance of [Left] or [Right]. | |
/// | |
/// [Left] is used for "failure". | |
/// [Right] is used for "success". | |
abstract class Either<L, R> { | |
const Either(); | |
/// Represents the left side of [Either] class which by convention is a "Failure". | |
bool get isLeft => this is Left<L, R>; | |
/// Represents the right side of [Either] class which by convention is a "Success" | |
bool get isRight => this is Right<L, R>; | |
/// Get [Left] value, may throw an exception when the value is [Right] | |
L get left => this.fold<L>( | |
(value) => value, | |
(right) => throw Exception( | |
'Illegal use. You should check isLeft before calling')); | |
/// Get [Right] value, may throw an exception when the value is [Left] | |
R get right => this.fold<R>( | |
(left) => throw Exception( | |
'Illegal use. You should check isRight before calling'), | |
(value) => value); | |
/// Transform values of [Left] and [Right] | |
Either<TL, TR> either<TL, TR>( | |
TL Function(L left) fnL, TR Function(R right) fnR); | |
/// Transform value of [Right] when transformation may be finished with an error | |
Either<L, TR> then<TR>(Either<L, TR> Function(R right) fnR); | |
/// Transform value of [Right] when transformation may be finished with an error | |
Future<Either<L, TR>> thenAsync<TR>( | |
FutureOr<Either<L, TR>> Function(R right) fnR); | |
/// Transform value of [Left] when transformation may be finished with an [Right] | |
Either<TL, R> thenLeft<TL>(Either<TL, R> Function(L left) fnL); | |
/// Transform value of [Left] when transformation may be finished with an [Right] | |
Future<Either<TL, R>> thenLeftAsync<TL>( | |
FutureOr<Either<TL, R>> Function(L left) fnL); | |
/// Transform value of [Right] | |
Either<L, TR> map<TR>(TR Function(R right) fnR); | |
/// Transform value of [Left] | |
Either<TL, R> mapLeft<TL>(TL Function(L left) fnL); | |
/// Transform value of [Right] | |
Future<Either<L, TR>> mapAsync<TR>(FutureOr<TR> Function(R right) fnR); | |
/// Transform value of [Left] | |
Future<Either<TL, R>> mapLeftAsync<TL>(FutureOr<TL> Function(L left) fnL); | |
/// Fold [Left] and [Right] into the value of one type | |
T fold<T>(T Function(L left) fnL, T Function(R right) fnR); | |
/// Swap [Left] and [Right] | |
Either<R, L> swap() => fold((left) => Right(left), (right) => Left(right)); | |
/// Constructs a new [Either] from a function that might throw | |
static Either<L, R> tryCatch<L, R, Err extends Object>( | |
L Function(Err err) onError, R Function() fnR) { | |
try { | |
return Right(fnR()); | |
} on Err catch (e) { | |
return Left(onError(e)); | |
} | |
} | |
/// Constructs a new [Either] from a function that might throw | |
/// | |
/// simplified version of [Either.tryCatch] | |
/// | |
/// ```dart | |
/// final fileOrError = Either.tryExcept<FileError>(() => /* maybe throw */); | |
/// ``` | |
static Either<Err, R> tryExcept<Err extends Object, R>(R Function() fnR) { | |
try { | |
return Right(fnR()); | |
} on Err catch (e) { | |
return Left(e); | |
} | |
} | |
/// If the condition is satify then return [rightValue] in [Right] else [leftValue] in [Left] | |
static Either<L, R> cond<L, R>(bool test, L leftValue, R rightValue) => | |
test ? Right(rightValue) : Left(leftValue); | |
/// If the condition is satify then return [rightValue] in [Right] else [leftValue] in [Left] | |
static Either<L, R> condLazy<L, R>( | |
bool test, Lazy<L> leftValue, Lazy<R> rightValue) => | |
test ? Right(rightValue()) : Left(leftValue()); | |
@override | |
bool operator ==(Object obj) { | |
return this.fold( | |
(left) => obj is Left && left == obj.value, | |
(right) => obj is Right && right == obj.value, | |
); | |
} | |
@override | |
int get hashCode => fold((left) => left.hashCode, (right) => right.hashCode); | |
} | |
/// Used for "failure" | |
class Left<L, R> extends Either<L, R> { | |
final L value; | |
const Left(this.value); | |
@override | |
Either<TL, TR> either<TL, TR>( | |
TL Function(L left) fnL, TR Function(R right) fnR) { | |
return Left<TL, TR>(fnL(value)); | |
} | |
@override | |
Either<L, TR> then<TR>(Either<L, TR> Function(R right) fnR) { | |
return Left<L, TR>(value); | |
} | |
@override | |
Future<Either<L, TR>> thenAsync<TR>( | |
FutureOr<Either<L, TR>> Function(R right) fnR) { | |
return Future.value(Left<L, TR>(value)); | |
} | |
@override | |
Either<TL, R> thenLeft<TL>(Either<TL, R> Function(L left) fnL) { | |
return fnL(value); | |
} | |
@override | |
Future<Either<TL, R>> thenLeftAsync<TL>( | |
FutureOr<Either<TL, R>> Function(L left) fnL) { | |
return Future.value(fnL(value)); | |
} | |
@override | |
Either<L, TR> map<TR>(TR Function(R right) fnR) { | |
return Left<L, TR>(value); | |
} | |
@override | |
Either<TL, R> mapLeft<TL>(TL Function(L left) fnL) { | |
return Left<TL, R>(fnL(value)); | |
} | |
@override | |
Future<Either<L, TR>> mapAsync<TR>(FutureOr<TR> Function(R right) fnR) { | |
return Future.value(Left<L, TR>(value)); | |
} | |
@override | |
Future<Either<TL, R>> mapLeftAsync<TL>(FutureOr<TL> Function(L left) fnL) { | |
return Future.value(fnL(value)).then((value) => Left<TL, R>(value)); | |
} | |
@override | |
T fold<T>(T Function(L left) fnL, T Function(R right) fnR) { | |
return fnL(value); | |
} | |
} | |
/// Used for "success" | |
class Right<L, R> extends Either<L, R> { | |
final R value; | |
const Right(this.value); | |
@override | |
Either<TL, TR> either<TL, TR>( | |
TL Function(L left) fnL, TR Function(R right) fnR) { | |
return Right<TL, TR>(fnR(value)); | |
} | |
@override | |
Either<L, TR> then<TR>(Either<L, TR> Function(R right) fnR) { | |
return fnR(value); | |
} | |
@override | |
Future<Either<L, TR>> thenAsync<TR>( | |
FutureOr<Either<L, TR>> Function(R right) fnR) { | |
return Future.value(fnR(value)); | |
} | |
@override | |
Either<TL, R> thenLeft<TL>(Either<TL, R> Function(L left) fnL) { | |
return Right<TL, R>(value); | |
} | |
@override | |
Future<Either<TL, R>> thenLeftAsync<TL>( | |
FutureOr<Either<TL, R>> Function(L left) fnL) { | |
return Future.value(Right<TL, R>(value)); | |
} | |
@override | |
Either<L, TR> map<TR>(TR Function(R right) fnR) { | |
return Right<L, TR>(fnR(value)); | |
} | |
@override | |
Either<TL, R> mapLeft<TL>(TL Function(L left) fnL) { | |
return Right<TL, R>(value); | |
} | |
@override | |
Future<Either<L, TR>> mapAsync<TR>(FutureOr<TR> Function(R right) fnR) { | |
return Future.value(fnR(value)).then((value) => Right<L, TR>(value)); | |
} | |
@override | |
Future<Either<TL, R>> mapLeftAsync<TL>(FutureOr<TL> Function(L left) fnL) { | |
return Future.value(Right<TL, R>(value)); | |
} | |
@override | |
T fold<T>(T Function(L left) fnL, T Function(R right) fnR) { | |
return fnR(value); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
abstract class Option<E> { | |
const Option(); | |
bool get _isDefined; | |
bool get _isEmpty => !_isDefined; | |
bool isNone() => _isEmpty; | |
bool isSome() => _isDefined; | |
E getOrElse(E defaultValue); | |
Option<R> map<R>(R Function(E value) f); | |
Option<R> flatMap<R>(Option<R> Function(E value) f); | |
R fold<R>(R Function() onEmpty, R Function(E value) onDefined); | |
static Option<E> fromNullable<E>(E? value) { | |
if (value != null) return Some(value); | |
return const None(); | |
} | |
} | |
class None<E> extends Option<E> { | |
const None(); | |
@override | |
bool get _isDefined => false; | |
@override | |
E getOrElse(E defaultValue) => defaultValue; | |
@override | |
Option<R> map<R>(R Function(E value) f) => const None(); | |
@override | |
Option<R> flatMap<R>(Option<R> Function(E value) f) => const None(); | |
@override | |
R fold<R>(R Function() onEmpty, R Function(E value) onDefined) => onEmpty(); | |
} | |
class Some<E> extends Option<E> { | |
final E value; | |
const Some(this.value); | |
@override | |
bool get _isDefined => true; | |
@override | |
E getOrElse(E defaultValue) => value is Never ? defaultValue : value; | |
@override | |
Option<R> map<R>(R Function(E value) f) => Some(f(value)); | |
@override | |
Option<R> flatMap<R>(Option<R> Function(E value) f) => f(value); | |
@override | |
R fold<R>(R Function() onEmpty, R Function(E value) onDefined) => | |
onDefined(value); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class ValueFailure<T> { | |
final T _value; | |
final String _message; | |
const ValueFailure(T value, String message) | |
: _value = value, | |
_message = message; | |
T? get value => _value; | |
String get message => _message; | |
@override | |
bool operator ==(Object other) => | |
identical(this, other) || | |
other is ValueFailure<T> && | |
other.value == value && | |
other.message == message; | |
@override | |
int get hashCode => _value.hashCode ^ _message.hashCode; | |
@override | |
String toString() => 'ValueFailure(value: $_value, message: $_message)'; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'either_type.dart'; | |
import 'value_failure.dart'; | |
import 'option_type.dart'; | |
abstract class ValueObject<T> { | |
const ValueObject(); | |
Either<ValueFailure<T>, T> get value; | |
bool get isValid => value.isRight; | |
T getOrCrash() => value.fold( | |
(f) => throw GroundException('Encountered a ValueFailure :: $f'), | |
(r) => r); | |
Option<T> get optionValue => | |
value.fold((_) => const None(), (value) => Some(value)); | |
Option<ValueFailure<T>> get failureOrNone => value.fold( | |
(left) => Some(left), | |
(right) => const None(), | |
); | |
@override | |
bool operator ==(Object other) { | |
if (identical(this, other)) return true; | |
return other is ValueObject<T> && other.value == value; | |
} | |
@override | |
int get hashCode => value.hashCode; | |
@override | |
String toString() => 'ValueObject($value)'; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment