
//
// Maybe[T]
//

variant Maybe[T] (Nothing, T);

record Nothing ();



//
// constructors
//

[T]
inline overload Maybe(x:T) = Maybe[T](x);

[T]
inline overload Maybe[T]() = Maybe[T](Nothing());

inline nothing(T) = Maybe[T]();

Maybe?(T) = false;
[T] overload Maybe?(static Maybe[T]) = true;


//
// nothing?
//

inline nothing?(x) = variantIs?(x, Nothing);
inline just?(x) = variantIs?(x, Type(just(x)));

[T]
inline just(forward m: Maybe[T]) = forward variantAs(m, T);
[T]
inline overload just(m: Maybe[T], dflt: T) = maybe(m, v => v, () => dflt);


//
// maybe function
//

private define maybeValue;
inline overload maybeValue(T, forward x, thenFn, ..elseFn) = forward ..thenFn(x);
inline overload maybeValue(T, forward n: Nothing, thenFn, elseFn) = forward ..elseFn();


// if the thenFn returns Maybe[T]s, have the default else case return nothing(T)s
[T, F | allValues?(Maybe?, ..Type(..typeToRValue(F)(typeToRValue(T))))]
inline overload maybeValue(static T, forward n: Nothing, thenFn:F)
    = ..mapValues(call, ..Type(..thenFn(typeToRValue(T))));

[T]
inline maybe(forward m:Maybe[T], thenFn, ..elseFn)
    = forward ..maybeValue(static T, *m, thenFn, ..elseFn);



//
// require function
//

record ValueMissing ();
instance Exception (ValueMissing);

define require;

[T]
inline overload require(ref m:Maybe[T], fn) {
    alias Ts = ReturnType(..fn(typeToLValue(T)));
    return forward ..maybe(m, fn, () => forward ..missing(..Ts));
}

[T]
inline overload require(rvalue m:Maybe[T], fn) {
    alias Ts = ReturnType(..fn(typeToRValue(T)));
    return forward ..maybe(move(m), fn, () => forward ..missing(..Ts));
}

[T]
inline overload require(forward m:Maybe[T]) = forward require(m, identity);

private inline missing(..returnTypes) {
    if (true) throw ValueMissing();
    // allow the compiler to infer return types
    return forward ..returnTypesToValues(..returnTypes);
}

private inline identity(forward ..args) = forward ..args;
