Last active
July 19, 2016 05:21
-
-
Save Sgeo/dff922fdbabffa9dd613f25cfd60146e to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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