namespace Option =

  function is_none(o : option('a)) : bool = switch(o)
    None => true
    Some(_) => false

  function is_some(o : option('a)) : bool = switch(o)
    None => false
    Some(_) => true

/** Catamorphism on `option`. Also known as inlined pattern matching.
 */
  function match(n : 'b, s : 'a => 'b, o : option('a)) : 'b = switch(o)
    None => n
    Some(x) => s(x)

/** Escape option providing default if `None`
 */
  function default(def : 'a, o : option('a)) : 'a = match(def, (x) => x, o)

/** Assume it is `Some`
 */
  function force(o : option('a)) : 'a = switch(o)
    None    => abort("Forced None value")
    Some(x) => x

/** Assume it is `Some` with custom error message
 */
  function force_msg(o : option('a), err : string) : 'a = switch(o)
    None    => abort(err)
    Some(x) => x

  function contains(e : 'a, o : option('a)) = o == Some(e)

  function on_elem(o : option('a), f : 'a => unit) : unit = match((), f, o)

  function map(f : 'a => 'b, o : option('a)) : option('b) = switch(o)
    None => None
    Some(x) => Some(f(x))

  function map2(f : ('a, 'b) => 'c
               , o1 : option('a)
               , o2 : option('b)
               ) : option('c) = switch((o1, o2))
    (Some(x1), Some(x2)) => Some(f(x1, x2))
    _ => None

  function map3( f : ('a, 'b, 'c) => 'd
               , o1 : option('a)
               , o2 : option('b)
               , o3 : option('c)
               ) : option('d) = switch((o1, o2, o3))
    (Some(x1), Some(x2), Some(x3)) => Some(f(x1, x2, x3))
    _ => None

/** Like `map`, but the function is in `option`
 */
  function app_over(f : option ('a => 'b), o : option('a)) : option('b) = switch((f, o))
    (Some(ff), Some(xx)) => Some(ff(xx))
    _ => None

/** Monadic bind
 */
  function flat_map(f : 'a => option('b), o : option('a)) : option('b) = switch(o)
    None => None
    Some(x) => f(x)


  function to_list(o : option('a)) : list('a) = switch(o)
    None => []
    Some(x) => [x]

/** Turns list of options into a list of elements that are under `Some`s.
 * Safe.
 */
  function filter_options(l : list(option('a))) : list('a) = switch(l)
    []         => []
    None::t    => filter_options(t)
    Some(x)::t => x::filter_options(t)

/** Just like `filter_options` but requires all elements to be `Some` and returns
 * None if any of them is not
 */
  function seq_options(l : list (option('a))) : option (list('a)) = switch(l)
    []         => Some([])
    None::_    => None
    Some(x)::t => switch(seq_options(t))
      None => None
      Some(st) => Some(x::st)


/** Choose `Some` out of two if possible
 */
  function choose(o1 : option('a), o2 : option('a)) : option('a) =
    if(is_some(o1)) o1 else o2

/** Choose `Some` from list of options if possible
 */
  function choose_first(l : list(option('a))) : option('a) = switch(l)
    [] => None
    None::t    => choose_first(t)
    Some(x)::_ => Some(x)
