Skip to content

Instantly share code, notes, and snippets.

@Sgeo
Last active July 19, 2016 05:21
Show Gist options
  • Save Sgeo/dff922fdbabffa9dd613f25cfd60146e to your computer and use it in GitHub Desktop.
Save Sgeo/dff922fdbabffa9dd613f25cfd60146e to your computer and use it in GitHub Desktop.
# Demonstration that any Elixir "lens" (function compatible with get_in and get_and_update_in) is equivalent to a lens represented in get/set style
# Modulo the fact that an Elixir lens cannot be written if there is no valid "get" operation
defmodule Optics do
defp id, do: fn x -> x end
defp old_and_new(new), do: fn old -> {old, new} end
def decompose(lens) do
[get: fn(obj) -> lens.(:get, obj, id) end,
set: fn(obj, new) -> lens.(:get_and_update, obj, old_and_new(new)) |> elem(1) end]
end
def decompose_easy(lens) do
[get: &get_in(&1, [lens]),
set: &put_in(&1, [lens], &2)]
end
def compose(accessors) do
fn
:get, obj, next ->
get_in(obj, accessors) |> next.()
:get_and_update, obj, next ->
get_and_update_in(obj, accessors, next)
end
end
@doc """
## Examples
iex> %{foo: 1} |> get_in [Optics.fetch(:foo)]
{:ok, 1}
iex> %{foo: 1} |> put_in [Optics.fetch(:foo)], {:ok, 5}
{foo: 5}
iex> %{foo: 1} |> put_in [Optics.fetch(:foo)], :error
%{}
"""
def fetch(key) do
fn
:get, data, next ->
next.(Map.fetch(to_map(data), key))
:get_and_update, data, next ->
value = Map.fetch(to_map(data), key)
case next.(value) do
{get, {:ok, update}} -> {get, Map.put(data, key, update)}
{get, :error} -> {get, Map.delete(data, key)}
:pop -> {value, Map.delete(data, key)}
end
end
end
@doc """
Focuses on an `{:ok, value} | :error`
If `{:ok, value}`, gives `value`
If `:error`, gives `default`
When setting, setting the focus to `default` gives error
Can be composed with `fetch(key)` and similar things
This will behave in a similar way to `Access.key/2`, with the exception that setting to default deletes.
It may make sense to have a fetch/2 that behaves similarly to Access.key/2, with the
## Examples
iex> foo = %{foo: 5}
iex> foo |> get_in [Optics.fetch(:foo), Optics.non(0)]
5
iex> foo |> update_in [Optics.fetch(:foo), Optics.non(0)], fn x -> x - 4 end
%{foo: 1}
iex> foo |> update_in [Optics.fetch(:foo), Optics.non(0)], fn x -> x - 5 end
%{}
iex> %{foo: 0} |> update_in [Optics.fetch(:foo), Optics.non(0)], fn x -> x end # May break lens laws if you treat %{foo: 0} as valid and distinct from %{}
%{}
"""
def non(default) do
fn
:get, {:ok, data}, next ->
next.(data)
:get, :error, next ->
next.(default)
:get_and_update, data, next ->
result_from_next = case data do
{:ok, value} -> next.(value)
:error -> next.(default)
end
case result_from_next do
{get, ^default} -> {get, :error}
{get, update} -> {get, {:ok, update}}
:pop -> :pop
end
end
end
defp to_map(nil), do: %{}
defp to_map(%{} = map), do: map
defp to_map(data), do: raise "Optics.key?/1 expected a map/struct or nil, got: #{inspect data}"
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment