defmodule Containers do @moduledoc """ Containers are functional data structures that help provide greater runtime safety and polymorphism. ## Protocols * `Appendable` - A container that provies an interface of `append`. Safe against `nil` values. Namely when passing a container with the value `nil` into either the first of second argument to `append`, the other value is not change and there is no runtime error. * `Mappable` - A container that provies an interface to `map`. When `map` is called on a container that has a `nil` value that container just passes through with out the mapping function being called, and this helps prevent runtime errors. * `Sequenceable` - A container that provides an interface of `next`. This allows the chaining of computations. * `Unwrappable` - A container that provides an interface to `safe` and `unsafe` unwrapping of inner value. Safe will need a default in case of `nil` value of container, helping prevent runtime errors. Unsafe will just return the value of the container regardless of a `nil` value potentially causing runtime errors * `Flattenable` - A container that provides an interface to `flatten` function. This allows for nested containers of the same container type to have the outter layer removed. Since these are protocols, and highly decoupled, a developer can implement them as needed on their own structs. """ @type appendable :: Containers.Text.t | Containers.Optional.t @type mappable :: Containers.Text.t | Containers.Optional.t | Containers.Result.t | list @type sequenceable :: Containers.Text.t | Containers.Optional.t | Containers.Result.t @type unwrappable :: Containers.Text.t | Containers.Optional.t | Containers.Result.t @type flattenable :: Containers.Result.t | Containers.Optional.t @doc """ Append two values of the Containers.Appendable protocol This is useful for chaning of appending appendable items safely. That is to say if there is a `nil` value being used like `nil <> " world!"` there will be a run time error. In this case the container for the string type will safe do concatenation. ## Examples iex> hello = Containers.Text.from_string("Hello") iex> world = Containers.Text.from_string(" world!") iex> Containers.append(hello, world) %Containers.Text{value: "Hello world!"} iex> hello = Containers.Text.from_string("Hello") iex> world = Containers.Text.from_string(" world!") iex> nil_string = Containers.Text.from_string(nil) iex> hello |> Containers.append(nil_string) |> Containers.append(world) %Containers.Text{value: "Hello world!"} """ @spec append(appendable(), appendable()) :: appendable() def append(v1, v2), do: Containers.Appendable.append(v1, v2) @doc """ Map some mapping function `f` on the innner value `n` number of mappable container structures deep. This is useful for when you have deeply nested containers that are mappable and you want to operator on the most inner value without have to unwrap. Note there is a provided `map2` function in this module for mapping two layers deeps, but if you needing mapping for a strucuture that is more deeply nested then 2 contianers, then this is the function you are looking for. """ @spec mapn(mappable, integer, (... -> any())) :: mappable def mapn(mappable, 1, f), do: map(mappable, f) def mapn(mappable, n, f) do map(mappable, fn x -> mapn(x, n - 1, f) end) end @doc """ map some function `f` of the some structure `s`. Works like the `Enum.map` but provides more polymorphic protocol, and does rely on the `Enumerable` protocol allowing use of just getting map without needing to implement the full `Enumerable` protocol. ## Examples iex> my_optional = Containers.Optional.to_optional(1) iex> Containers.map(my_optional, fn(i) -> i + 1 end) %Containers.Optional{value: 2} """ @spec map(mappable, (... -> any())) :: mappable def map(s, f), do: Containers.Mappable.map(s, f) @doc """ map some function `f` over some nested strcutre `s`. This is useful for when you have a mappable in another mappable and you just want to use a mapping function on the inner value of the nested map. """ @spec map2(mappable, (... -> any())) :: mappable def map2(s, f), do: mapn(s, 2, f) @doc """ next is a function that will allow chaining of computations while passing the `value` of the last computation. """ @spec next(sequenceable(), (any() -> sequenceable())) :: sequenceable() def next(s, f), do: Containers.Sequenceable.next(s, f) @doc """ `>>>` is the infix operator for `next` ## Examples iex> import Containers iex> my_optional = Containers.Optional.to_optional(1) iex> my_optional >>> fn(i) -> Containers.Optional.to_optional(i + 1) end %Containers.Optional{value: 2} """ def s >>> f, do: Containers.Sequenceable.next(s, f) @doc """ safely unwrap the inner value of a container, proviing a default in case the value is `nil`. This is should help prevent runtime errors within a `|>` chain handling strings. ## Examples iex> my_string = Containers.Text.from_string("hello") iex> Containers.safe_unwrap(my_string, "this wont be needed") "hello" iex> my_nil_string = Containers.Text.from_string(nil) iex> Containers.safe_unwrap(my_nil_string, "This will be the value") "This will be the value" """ @spec safe_unwrap(unwrappable(), any()) :: any() def safe_unwrap(s, default), do: Containers.Unwrappable.safe(s, default) @doc """ unsafely unwrap the inner value of a continer. This may return nil so any guarantees against a runtime error no longer apply. ## Examples iex> my_string = Containers.Text.from_string("Hello") iex> Containers.unsafe_unwrap(my_string) "Hello" iex> my_nil_string = Containers.Text.from_string(nil) iex> Containers.unsafe_unwrap(my_nil_string) nil """ @spec unsafe_unwrap(unwrappable()) :: any() def unsafe_unwrap(s), do: Containers.Unwrappable.unsafe!(s) @doc """ concat a list of Containers that implement the Appendable protocol ``` hello_world = "hello_world" hello_world |> String.split("_") |> Enum.map(&String.capitalize/1) |> Enum.map(&Containers.Text.from_string/1) |> Containers.concat() |> Containers.safe_unwrap("") "HelloWorld" ``` ## Examples iex> hello = Containers.Text.from_string("hello") iex> world = Containers.Text.from_string(" world") iex> excliam = Containers.Text.from_string("!") iex> Containers.concat([hello, world, excliam]) %Containers.Text{value: "hello world!"} """ @spec concat(list(appendable)) :: appendable() def concat(appendables) do appendables |> Enum.reverse |> Enum.reduce(&append/2) end @doc """ This is useful for when you have a container that inner structure of that same container, and you want to flatten that down to one level. ## Examples iex> nested = %Containers.Optional{value: %Containers.Optional{value: "hello"}} iex> Containers.flatten(nested) %Containers.Optional{value: "hello"} """ @spec flatten(flattenable) :: flattenable def flatten(flat), do: Containers.Flattenable.flatten(flat) end