Packages

A functional optics library. Create and compose lenses to view, set, and modify data inside arbitrarily nested maps, lists, and tuples.

Current section

Files

Jump to
focus lib prism prism.ex
Raw

lib/prism/prism.ex

defmodule Prism do
alias Focus.Types
@moduledoc """
Prisms are like lenses, but used when the view focused on may not exist.
This includes sum types; although not backed by an explicit Maybe type,
the [{:ok, any} | {:error}] convention is explicitly supported as a value that
a prism can focus on.
"""
@enforce_keys [:get, :put]
defstruct [:get, :put]
@type t :: %Prism{
get: ((any) -> any),
put: (((any) -> any) -> any)
}
@doc """
A prism that matches an {:ok, _} tuple.
## Examples
iex> ok = Prism.ok
iex> ok |> Focus.view({:ok, 5})
{:ok, 5}
iex> ok |> Focus.set({:ok, 5}, "Banana")
{:ok, "Banana"}
iex> ok |> Focus.view({:error, :oops})
{:error, {:prism, :bad_path}}
"""
@spec ok() :: Prism.t
def ok() do
%Prism{
get: fn s -> get_ok(s) end,
put: fn s ->
fn f ->
set_ok(s, f)
end
end
}
end
defp get_ok({:ok, x}), do: x
defp get_ok({:error, _}), do: nil
defp set_ok({:ok, _x}, f), do: {:ok, f}
@doc """
A prism that matches an {:error, _} tuple.
Note that on a successful match, view/set/over will
return {:ok, _}
## Examples
iex> error = Prism.error
iex> error |> Focus.view({:error, 5})
{:ok, 5}
iex> error |> Focus.set({:error, 5}, "Banana")
{:ok, "Banana"}
iex> error |> Focus.view({:ok, :oops})
{:error, {:prism, :bad_path}}
"""
@spec error() :: Prism.t
def error() do
%Prism{
get: fn s -> get_error(s) end,
put: fn s ->
fn f ->
set_error(s, f)
end
end
}
end
defp get_error({:error, x}), do: x
defp get_error({:ok, _}), do: nil
defp set_error({:error, _x}, f), do: {:ok, f}
defimpl Focusable do
@doc """
Get a piece of a data structure that a prism focuses on;
returns {:ok, data} | {:error, :bad_prism_path}
## Examples
iex> ok = Prism.ok
iex> ok |> Focus.view({:ok, 5})
{:ok, 5}
"""
@spec view(Prism.t, Types.traversable) :: {:error, {:prism, :bad_path}} | {:ok, any}
def view(%Prism{get: get}, structure) do
res = get.(structure)
case res do
nil -> {:error, {:prism, :bad_path}}
_ -> {:ok, res}
end
end
@doc """
Modify the part of a data structure that a prism focuses on.
## Examples
iex> ok = Prism.ok
iex> ok |> Focus.over({:ok, "banana"}, &String.upcase/1)
{:ok, "BANANA"}
"""
@spec over(Prism.t, Types.traversable, (any -> any)) :: Types.traversable
def over(%Prism{put: put} = prism, structure, f) do
with {:ok, data_view} <- view(prism, structure) do
put.(structure).(f.(data_view))
end
end
@doc """
Update the part of a data structure the prism focuses on.
## Examples
iex> ok = Prism.ok
iex> ok |> Focus.set({:ok, "banana"}, "pineapple")
{:ok, "pineapple"}
"""
@spec set(Prism.t, Types.traversable, any) :: Types.traversable
def set(%Prism{put: put} = prism, structure, val) do
with {:ok, _data_view} <- view(prism, structure) do
put.(structure).(val)
end
end
end
end