Skip to content

Commit d882164

Browse files
committed
add experimental fasten hook
1 parent ac3340e commit d882164

File tree

4 files changed

+155
-4
lines changed

4 files changed

+155
-4
lines changed

bench/struct_bench.exs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,29 @@ defmodule Example do
1414
field :b, :float
1515
field :c, {:map, :integer}
1616
field :d, Embedded
17+
field :e, {:map, :integer}, default: %{}
18+
end
19+
end
20+
21+
defmodule EmbeddedFast do
22+
use Construct
23+
use Construct.Hooks.Fasten
24+
25+
structure do
26+
field :e
27+
end
28+
end
29+
30+
defmodule ExampleFast do
31+
use Construct
32+
use Construct.Hooks.Fasten
33+
34+
structure do
35+
field :a
36+
field :b, :float
37+
field :c, {:map, :integer}
38+
field :d, EmbeddedFast
39+
field :e, {:map, :integer}, default: %{}
1740
end
1841
end
1942

@@ -22,6 +45,10 @@ Benchee.run(
2245
"make" => fn ->
2346
{:ok, _} = Example.make(%{a: "test", b: 1.42, c: %{a: 0, b: 42}, d: %{e: "embeds"}})
2447
end,
48+
49+
"make fasten hook" => fn ->
50+
{:ok, _} = ExampleFast.make(%{a: "test", b: 1.42, c: %{a: 0, b: 42}, d: %{e: "embeds"}})
51+
end,
2552
},
2653
time: 3,
2754
memory_time: 3,

lib/construct/compiler.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ defmodule Construct.Compiler do
55
alias Construct.Compiler.AST
66

77
@registry Construct.Registry
8-
@no_default :__construct_no_default__
8+
@no_default :__construct_no_default_value__
99

1010
def construct_module?(module) do
1111
registered_type?(module) || ensure_compiled?(module) && function_exported?(module, :__construct__, 1)

lib/construct/hooks/fasten.ex

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
defmodule Construct.Hooks.Fasten do
2+
defmacro __using__(_opts \\ []) do
3+
quote do
4+
structure_compile_hook :post do
5+
Module.eval_quoted(__MODULE__, Construct.Hooks.Fasten.__compile__(__MODULE__, Enum.reverse(@fields)))
6+
7+
defoverridable make: 2
8+
end
9+
end
10+
end
11+
12+
def __compile__(module, fields) do
13+
cast_defs =
14+
Enum.map(fields, fn({name, type, opts}) ->
15+
function_name = :"__cast_#{name}__"
16+
17+
{default_before_clause, default_after_clause} =
18+
case Keyword.get(opts, :default) do
19+
:__construct_no_default_value__ ->
20+
clause_after =
21+
quote do
22+
defp unquote(function_name)(_, _) do
23+
{:error, %{unquote(name) => :missing}}
24+
end
25+
end
26+
27+
{[], clause_after}
28+
29+
nil ->
30+
clause_after =
31+
quote do
32+
defp unquote(function_name)(_, _) do
33+
{:error, %{unquote(name) => :missing}}
34+
end
35+
end
36+
37+
{[], clause_after}
38+
39+
term ->
40+
term = Macro.escape(term)
41+
42+
clause_before =
43+
quote do
44+
defp unquote(function_name)(%{unquote(to_string(name)) => term}, _opts) when term == unquote(term) do
45+
{:ok, unquote(term)}
46+
end
47+
48+
defp unquote(function_name)(%{unquote(name) => term}, _opts) when term == unquote(term) do
49+
{:ok, unquote(term)}
50+
end
51+
end
52+
53+
clause_after =
54+
quote do
55+
defp unquote(function_name)(_, _) do
56+
{:ok, unquote(term)}
57+
end
58+
end
59+
60+
{clause_before, clause_after}
61+
end
62+
63+
cast_clause =
64+
quote do
65+
defp unquote(function_name)(%{unquote(to_string(name)) => term}, opts) do
66+
Construct.Type.cast(unquote(type), term, opts)
67+
end
68+
69+
defp unquote(function_name)(%{unquote(name) => term}, opts) do
70+
Construct.Type.cast(unquote(type), term, opts)
71+
end
72+
end
73+
74+
default_before_clause |> merge_blocks(cast_clause) |> merge_blocks(default_after_clause)
75+
end)
76+
77+
cast_defs =
78+
Enum.reduce(cast_defs, {:__block__, [], []}, fn(ast, acc) ->
79+
merge_blocks(acc, ast)
80+
end)
81+
82+
with_body =
83+
Enum.map(fields, fn({name, _type, _opts}) ->
84+
{name, Macro.var(name, nil)}
85+
end)
86+
87+
with_body =
88+
quote do
89+
{:ok, struct(unquote(module), unquote(with_body))}
90+
end
91+
92+
with_matches =
93+
Enum.map(fields, fn({name, _type, _opts}) ->
94+
quote do
95+
{:ok, unquote(Macro.var(name, nil))} <- unquote(:"__cast_#{name}__")(params, opts)
96+
end
97+
end)
98+
99+
with_ast = {:with, [], with_matches ++ [[do: with_body]]}
100+
101+
make_ast =
102+
quote do
103+
def make(params, opts) do
104+
unquote(with_ast)
105+
end
106+
end
107+
108+
merge_blocks(make_ast, cast_defs)
109+
end
110+
111+
defp merge_blocks(a, b) do
112+
{:__block__, [], block_content(a) ++ block_content(b)}
113+
end
114+
115+
defp block_content({:__block__, [], content}) do
116+
content
117+
end
118+
119+
defp block_content({_, _, _} = expr) do
120+
[expr]
121+
end
122+
123+
defp block_content([]) do
124+
[]
125+
end
126+
end

lib/construct/type.ex

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -468,11 +468,9 @@ defmodule Construct.Type do
468468
{:ok, Enum.reverse(acc)}
469469
end
470470

471-
defp map(list, type, fun, acc, opts \\ [])
472-
473471
defp map([{key, value} | t], type, fun, acc, opts) do
474472
case fun.(type, value, opts) do
475-
{:ok, value} -> map(t, type, fun, Map.put(acc, key, value))
473+
{:ok, value} -> map(t, type, fun, Map.put(acc, key, value), opts)
476474
{:error, reason} -> {:error, reason}
477475
:error -> :error
478476
end

0 commit comments

Comments
 (0)