Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions bench/struct_bench.exs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,29 @@ defmodule Example do
field :b, :float
field :c, {:map, :integer}
field :d, Embedded
field :e, {:map, :integer}, default: %{}
end
end

defmodule EmbeddedFast do
use Construct
use Construct.Hooks.Fasten

structure do
field :e
end
end

defmodule ExampleFast do
use Construct
use Construct.Hooks.Fasten

structure do
field :a
field :b, :float
field :c, {:map, :integer}
field :d, EmbeddedFast
field :e, {:map, :integer}, default: %{}
end
end

Expand All @@ -22,6 +45,10 @@ Benchee.run(
"make" => fn ->
{:ok, _} = Example.make(%{a: "test", b: 1.42, c: %{a: 0, b: 42}, d: %{e: "embeds"}})
end,

"make fasten hook" => fn ->
{:ok, _} = ExampleFast.make(%{a: "test", b: 1.42, c: %{a: 0, b: 42}, d: %{e: "embeds"}})
end,
},
time: 3,
memory_time: 3,
Expand Down
2 changes: 1 addition & 1 deletion lib/construct/compiler.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ defmodule Construct.Compiler do
alias Construct.Compiler.AST

@registry Construct.Registry
@no_default :__construct_no_default__
@no_default :__construct_no_default_value__

def construct_module?(module) do
registered_type?(module) || ensure_compiled?(module) && function_exported?(module, :__construct__, 1)
Expand Down
126 changes: 126 additions & 0 deletions lib/construct/hooks/fasten.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
defmodule Construct.Hooks.Fasten do
defmacro __using__(_opts \\ []) do
quote do
structure_compile_hook :post do
Module.eval_quoted(__MODULE__, Construct.Hooks.Fasten.__compile__(__MODULE__, Enum.reverse(@fields)))

defoverridable make: 2
end
end
end

def __compile__(module, fields) do
cast_defs =
Enum.map(fields, fn({name, type, opts}) ->
function_name = :"__cast_#{name}__"

{default_before_clause, default_after_clause} =
case Keyword.get(opts, :default) do
:__construct_no_default_value__ ->
clause_after =
quote do
defp unquote(function_name)(_, _) do
{:error, %{unquote(name) => :missing}}
end
end

{[], clause_after}

nil ->
clause_after =
quote do
defp unquote(function_name)(_, _) do
{:error, %{unquote(name) => :missing}}
end
end

{[], clause_after}

term ->
term = Macro.escape(term)

clause_before =
quote do
defp unquote(function_name)(%{unquote(to_string(name)) => term}, _opts) when term == unquote(term) do
{:ok, unquote(term)}
end

defp unquote(function_name)(%{unquote(name) => term}, _opts) when term == unquote(term) do
{:ok, unquote(term)}
end
end

clause_after =
quote do
defp unquote(function_name)(_, _) do
{:ok, unquote(term)}
end
end

{clause_before, clause_after}
end

cast_clause =
quote do
defp unquote(function_name)(%{unquote(to_string(name)) => term}, opts) do
Construct.Type.cast(unquote(type), term, opts)
end

defp unquote(function_name)(%{unquote(name) => term}, opts) do
Construct.Type.cast(unquote(type), term, opts)
end
end

default_before_clause |> merge_blocks(cast_clause) |> merge_blocks(default_after_clause)
end)

cast_defs =
Enum.reduce(cast_defs, {:__block__, [], []}, fn(ast, acc) ->
merge_blocks(acc, ast)
end)

with_body =
Enum.map(fields, fn({name, _type, _opts}) ->
{name, Macro.var(name, nil)}
end)

with_body =
quote do
{:ok, struct(unquote(module), unquote(with_body))}
end

with_matches =
Enum.map(fields, fn({name, _type, _opts}) ->
quote do
{:ok, unquote(Macro.var(name, nil))} <- unquote(:"__cast_#{name}__")(params, opts)
end
end)

with_ast = {:with, [], with_matches ++ [[do: with_body]]}

make_ast =
quote do
def make(params, opts) do
unquote(with_ast)
end
end

merge_blocks(make_ast, cast_defs)
end

defp merge_blocks(a, b) do
{:__block__, [], block_content(a) ++ block_content(b)}
end

defp block_content({:__block__, [], content}) do
content
end

defp block_content({_, _, _} = expr) do
[expr]
end

defp block_content([]) do
[]
end
end
4 changes: 1 addition & 3 deletions lib/construct/type.ex
Original file line number Diff line number Diff line change
Expand Up @@ -468,11 +468,9 @@ defmodule Construct.Type do
{:ok, Enum.reverse(acc)}
end

defp map(list, type, fun, acc, opts \\ [])

defp map([{key, value} | t], type, fun, acc, opts) do
case fun.(type, value, opts) do
{:ok, value} -> map(t, type, fun, Map.put(acc, key, value))
{:ok, value} -> map(t, type, fun, Map.put(acc, key, value), opts)
{:error, reason} -> {:error, reason}
:error -> :error
end
Expand Down