Last active
August 29, 2015 14:05
-
-
Save lafka/0c7fc4cc558c6b6a0bb8 to your computer and use it in GitHub Desktop.
Macro for modifying deep lists
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
defmodule Lens do | |
@moduledoc """ | |
Utility to dynamically put nested keys into maps | |
``` | |
import Lens, stringkeys?: false | |
a = %{} | |
a.b.c.d <- "i'm a dddddd" | |
IO.inspect a # => %{b: %{c: %{d: "i'm a dddddd}}} | |
``` | |
""" | |
defmacro __using__(options \\ []) do | |
keyfun = case options[:stringkeys?] do | |
true -> | |
quote(do: &Atom.to_string/1) | |
_ -> | |
quote(do: fn(a) -> a end) | |
end | |
quote do | |
defmacro left <- right do | |
[var | path] = Lens.compactpath(left, [], unquote(keyfun)) | |
setter = Lens.gensetpath(var, path, right) | |
quote(do: unquote(var) = unquote(setter)) | |
end | |
end | |
end | |
@doc """ | |
Helper to calculate the path of the setter reference | |
""" | |
def compactpath({{:., _, [Access, :get]}, _, [a, [_|_] = b]}, [], keyfun), do: | |
compactpath(a, b, keyfun) | |
def compactpath({{:., _, [Access, :get]}, _, [a, b]}, [], keyfun), do: | |
compactpath(a, [b], keyfun) | |
def compactpath({{:., _, [a,b]}, _, _}, [], keyfun), do: | |
compactpath(a, [keyfun.(b)], keyfun) | |
def compactpath({{:., _, [Access, :get]}, _, [a, b]}, acc, keyfun), do: | |
compactpath(a, [b|acc], keyfun) | |
def compactpath({{:., _, [a,b]}, _, _}, acc, keyfun), do: | |
compactpath(a, [keyfun.(b) | acc], keyfun) | |
def compactpath([b | rest], acc, keyfun), do: | |
compactpath(rest, [b|acc], keyfun) | |
def compactpath(b, acc, _keyfun) do | |
[b|acc] | |
end | |
@doc """ | |
Helper to generate setter function AST | |
""" | |
def gensetpath(var, [k], v), do: | |
quote(do: Dict.put(unquote(var), unquote(k), unquote(v))) | |
def gensetpath(var, [k|rest], v) do | |
nest = gensetpath(quote(do: unquote(var)[unquote(k)] || %{}), rest, v) | |
quote(do: Dict.put(unquote(var), unquote(k), unquote(nest))) | |
end | |
def setpath(var, [k], v), do: | |
Dict.put(var, k, v) | |
def setpath(var, [k|rest], v), do: | |
Dict.put(var, k, setpath(var[k] || %{}, rest, v)) | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment