defmodule Algae.Maybe do @moduledoc ~S""" The sum of `Algae.Maybe.Just` and `Algae.Maybe.Nothing`. Maybe represents the presence or absence of something. Please note that `nil` is actually a value, as it can be passed to functions! `nil` is not bottom! ## Examples iex> [1,2,3] ...> |> List.first() ...> |> case do ...> nil -> new() ...> head -> new(head) ...> end %Algae.Maybe.Just{just: 1} iex> [] ...> |> List.first() ...> |> case do ...> nil -> new() ...> head -> new(head) ...> end %Algae.Maybe.Nothing{} """ import Algae alias Algae.Maybe.{Just, Nothing} defsum do defdata Nothing :: none() defdata Just :: any() end @doc ~S""" Put no value into the `Maybe` context (ie: make it a `Nothing`) ## Examples iex> new() %Algae.Maybe.Nothing{} """ @spec new() :: Nothing.t() defdelegate new, to: Nothing, as: :new @doc ~S""" Put a value into the `Maybe` context (ie: make it a `Just`) ## Examples iex> new(9) %Algae.Maybe.Just{just: 9} iex> new(nil) %Algae.Maybe.Just{just: nil} iex> new(nil, nothing: nil) %Algae.Maybe.Nothing{} iex> new(9, nothing: 9) %Algae.Maybe.Nothing{} iex> new(9, nothing: 1) %Algae.Maybe.Just{just: 9} """ @spec new(any(), [nothing: any()]) :: Just.t() | Nothing.t() def new(nothing_value, [nothing: nothing_value]), do: Nothing.new() def new(value, _), do: Just.new(value) @spec new(any()) :: Just.t() def new(value), do: Just.new(value) @doc """ Alias for `new(value, nothing: nil)`. ## Examples iex> from_nillable(9) %Algae.Maybe.Just{just: 9} iex> from_nillable(nil) %Algae.Maybe.Nothing{} """ @spec from_nillable(any()) :: Just.t() def from_nillable(value), do: new(value, nothing: nil) @doc """ Extract a value from a `Maybe`, falling back to a set value in the `Nothing` case. ## Examples iex> from_maybe(%Algae.Maybe.Nothing{}, else: 42) 42 iex> %Algae.Maybe.Just{just: 1955} |> from_maybe(else: 42) 1955 """ @spec from_maybe(t(), any()) :: any() def from_maybe(%Nothing{}, [else: fallback]), do: fallback def from_maybe(%Just{just: inner}, _), do: inner end alias Algae.Maybe.{Just, Nothing} import TypeClass use Witchcraft ############# # Generator # ############# defimpl TypeClass.Property.Generator, for: Algae.Maybe.Nothing do def generate(_), do: Nothing.new() end defimpl TypeClass.Property.Generator, for: Algae.Maybe.Just do def generate(_) do [1, 1.1, "", []] |> Enum.random() |> TypeClass.Property.Generator.generate() |> Just.new() end end ########## # Setoid # ########## definst Witchcraft.Setoid, for: Algae.Maybe.Nothing do def equivalent?(_, %Nothing{}), do: true def equivalent?(_, %Just{}), do: false end definst Witchcraft.Setoid, for: Algae.Maybe.Just do def equivalent?(%Just{just: a}, %Just{just: b}), do: Witchcraft.Setoid.equivalent?(a, b) def equivalent?(%Just{}, %Nothing{}), do: false end ####### # Ord # ####### definst Witchcraft.Ord, for: Algae.Maybe.Nothing do def compare(_, %Nothing{}), do: :equal def compare(_, %Just{}), do: :lesser end definst Witchcraft.Ord, for: Algae.Maybe.Just do custom_generator(_) do 1 |> TypeClass.Property.Generator.generate() |> Just.new() end def compare(%Just{just: a}, %Just{just: b}), do: Witchcraft.Ord.compare(a, b) def compare(%Just{}, %Nothing{}), do: :greater end ############# # Semigroup # ############# definst Witchcraft.Semigroup, for: Algae.Maybe.Nothing do def append(_, right), do: right end definst Witchcraft.Semigroup, for: Algae.Maybe.Just do custom_generator(_) do 1 |> TypeClass.Property.Generator.generate() |> Just.new() end def append(%Just{just: a}, %Just{just: b}), do: %Just{just: a <> b} def append(just, %Nothing{}), do: just end ########## # Monoid # ########## definst Witchcraft.Monoid, for: Algae.Maybe.Nothing do def empty(nothing), do: nothing end definst Witchcraft.Monoid, for: Algae.Maybe.Just do def empty(_), do: %Algae.Maybe.Nothing{} end ########### # Functor # ########### definst Witchcraft.Functor, for: Algae.Maybe.Nothing do def map(_, _), do: %Algae.Maybe.Nothing{} end definst Witchcraft.Functor, for: Algae.Maybe.Just do def map(%{just: data}, fun), do: data |> fun.() |> Algae.Maybe.Just.new() end ############ # Foldable # ############ definst Witchcraft.Foldable, for: Algae.Maybe.Nothing do def right_fold(_, seed, _), do: seed end definst Witchcraft.Foldable, for: Algae.Maybe.Just do def right_fold(%Just{just: inner}, seed, fun), do: fun.(inner, seed) end ############### # Traversable # ############### # Not traversable because we don't have enough type information for Nothing ######### # Apply # ######### definst Witchcraft.Apply, for: Algae.Maybe.Nothing do def convey(_, _), do: %Algae.Maybe.Nothing{} end definst Witchcraft.Apply, for: Algae.Maybe.Just do alias Algae.Maybe.{Just, Nothing} def convey(data, %Nothing{}), do: %Nothing{} def convey(data, %Just{just: fun}), do: map(data, fun) end ############### # Applicative # ############### definst Witchcraft.Applicative, for: Algae.Maybe.Nothing do def of(_, data), do: Just.new(data) end definst Witchcraft.Applicative, for: Algae.Maybe.Just do def of(_, data), do: Just.new(data) end ######### # Chain # ######### definst Witchcraft.Chain, for: Algae.Maybe.Nothing do def chain(_, _), do: %Nothing{} end definst Witchcraft.Chain, for: Algae.Maybe.Just do def chain(%{just: data}, link), do: link.(data) end ######### # Monad # ######### definst Witchcraft.Monad, for: Algae.Maybe.Nothing definst Witchcraft.Monad, for: Algae.Maybe.Just ########## # Extend # ########## definst Witchcraft.Extend, for: Algae.Maybe.Nothing do def nest(_), do: %Nothing{} end definst Witchcraft.Extend, for: Algae.Maybe.Just do def nest(inner), do: Just.new(inner) end