Skip to content

Commit 6b83044

Browse files
committed
poc for fasten hook
1 parent 21ecaf5 commit 6b83044

File tree

4 files changed

+162
-4
lines changed

4 files changed

+162
-4
lines changed

bench/struct_bench.exs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,49 @@ defmodule Example do
1414
field :b, :float
1515
field :c, {:map, :integer}
1616
field :d, Embedded
17+
field :e, {:map, :integer}, default: %{}
1718
end
1819
end
1920

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: %{}
40+
end
41+
end
42+
43+
# System.halt(0)
44+
45+
Example.make(%{a: "test", b: 1.42, c: %{a: 0, b: 42}, d: %{e: "embeds"}})
46+
|> IO.inspect()
47+
48+
ExampleFast.make(%{a: "test", b: 1.42, c: %{a: 0, b: 42}, d: %{e: "embeds"}})
49+
|> IO.inspect()
50+
2051
Benchee.run(
2152
%{
2253
"make" => fn ->
2354
{:ok, _} = Example.make(%{a: "test", b: 1.42, c: %{a: 0, b: 42}, d: %{e: "embeds"}})
2455
end,
56+
57+
"make optimized" => fn ->
58+
{:ok, _} = ExampleFast.make(%{a: "test", b: 1.42, c: %{a: 0, b: 42}, d: %{e: "embeds"}})
59+
end,
2560
},
2661
time: 3,
2762
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: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
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+
{default_before_clause, default_after_clause} =
16+
case Keyword.get(opts, :default) do
17+
:__construct_no_default_value__ ->
18+
clause_after =
19+
quote do
20+
def unquote(:"cast_#{name}")(_, _) do
21+
{:error, %{unquote(name) => :missing}}
22+
end
23+
end
24+
25+
{[], clause_after}
26+
27+
nil ->
28+
clause_after =
29+
quote do
30+
def unquote(:"cast_#{name}")(_, _) do
31+
{:error, %{unquote(name) => :missing}}
32+
end
33+
end
34+
35+
{[], clause_after}
36+
37+
term ->
38+
term = Macro.escape(term)
39+
40+
clause_before =
41+
quote do
42+
def unquote(:"cast_#{name}")(%{unquote(to_string(name)) => term}, _opts) when term == unquote(term) do
43+
{:ok, unquote(term)}
44+
end
45+
46+
def unquote(:"cast_#{name}")(%{unquote(name) => term}, _opts) when term == unquote(term) do
47+
{:ok, unquote(term)}
48+
end
49+
end
50+
51+
clause_after =
52+
quote do
53+
def unquote(:"cast_#{name}")(_, _) do
54+
{:ok, unquote(term)}
55+
end
56+
end
57+
58+
{clause_before, clause_after}
59+
end
60+
61+
cast_clause =
62+
quote do
63+
def unquote(:"cast_#{name}")(%{unquote(to_string(name)) => term}, opts) do
64+
Construct.Type.cast(unquote(type), term, opts)
65+
end
66+
67+
def unquote(:"cast_#{name}")(%{unquote(name) => term}, opts) do
68+
Construct.Type.cast(unquote(type), term, opts)
69+
end
70+
end
71+
72+
default_before_clause |> merge_blocks(cast_clause) |> merge_blocks(default_after_clause)
73+
end)
74+
75+
cast_defs =
76+
Enum.reduce(cast_defs, {:__block__, [], []}, fn(ast, acc) ->
77+
merge_blocks(acc, ast)
78+
end)
79+
80+
with_body =
81+
Enum.map(fields, fn({name, _type, _opts}) ->
82+
{name, Macro.var(name, nil)}
83+
end)
84+
85+
with_body =
86+
quote do
87+
{:ok, struct(unquote(module), unquote(with_body))}
88+
end
89+
90+
with_matches =
91+
Enum.map(fields, fn({name, _type, _opts}) ->
92+
quote do
93+
{:ok, unquote(Macro.var(name, nil))} <- unquote(:"cast_#{name}")(params, opts)
94+
end
95+
end)
96+
97+
with_ast = {:with, [], with_matches ++ [[do: with_body]]}
98+
99+
make_ast =
100+
quote do
101+
def make(params, opts) do
102+
unquote(with_ast)
103+
end
104+
end
105+
106+
merge_blocks(make_ast, cast_defs)
107+
|> tap(fn(ast) -> IO.puts(Macro.to_string(ast)) end)
108+
end
109+
110+
defp merge_blocks(a, b) do
111+
{:__block__, [], block_content(a) ++ block_content(b)}
112+
end
113+
114+
defp block_content({:__block__, [], content}) do
115+
content
116+
end
117+
118+
defp block_content({_, _, _} = expr) do
119+
[expr]
120+
end
121+
122+
defp block_content([]) do
123+
[]
124+
end
125+
end

lib/construct/type.ex

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

467-
defp map(list, type, fun, acc, opts \\ [])
468-
469467
defp map([{key, value} | t], type, fun, acc, opts) do
470468
case fun.(type, value, opts) do
471-
{:ok, value} -> map(t, type, fun, Map.put(acc, key, value))
469+
{:ok, value} -> map(t, type, fun, Map.put(acc, key, value), opts)
472470
{:error, reason} -> {:error, reason}
473471
:error -> :error
474472
end

0 commit comments

Comments
 (0)