Created
May 15, 2015 01:13
-
-
Save jcomellas/ee5cd5b7963dc6b79282 to your computer and use it in GitHub Desktop.
Elixir macro that dynamically adds type specs
This file contains 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
# Given a module defined in this way: | |
defmodule HL7.Composite do | |
# [...] | |
defmodule EI do | |
use HL7.Composite.Def | |
composite do | |
component :id, type: :binary, default: "" | |
component :namespace_id, type: :binary, default: "" | |
component :universal_id, type: :binary, default: "" | |
component :universal_id_type, type: :binary, default: "" | |
end | |
end | |
# [...] | |
end | |
# And a macro that defines the different parts of the module: | |
defmodule HL7.Composite.Def do | |
# [...] | |
defmacro __before_compile__(_env) do | |
quote do | |
defstruct unquote(Module.get_attribute(__CALLER__.module, :struct_fields) | |
|> Enum.reverse | |
|> Macro.escape) | |
# I'd like to dynamically define a type specification given a list of key-value pairs | |
# holding the name of a field as an atom and its type as an atom. So what we need to | |
# do is convert something like "{:id, :binary}" into an expression suitable for a type | |
# spec | |
# @type t :: %unquote(__CALLER__.module){ | |
# unquote(Module.get_attribute(__CALLER__.module, :components) | |
# |> Enum.map(fn {name, type} -> {name, Atom.to_string(type)} end) | |
# |> Enum.reverse | |
# |> Macro.escape)} | |
def keys(), do: | |
unquote(Module.get_attribute(__CALLER__.module, :components) | |
|> Enum.reverse | |
|> List.to_tuple | |
|> Macro.escape) | |
def valid?(%unquote(__CALLER__.module){}), do: | |
true | |
def valid?(_), do: | |
false | |
def from_item(item), do: | |
unquote(__MODULE__).from_item(%unquote(__CALLER__.module){}, keys(), item) | |
def to_iodata(map, options \\ []), do: | |
unquote(__MODULE__).to_iodata(map, keys(), options) | |
end | |
end | |
# [...] | |
end | |
# The desired output should look like this (the type specification is commented out): | |
defmodule HL7.Composite.EI do | |
defstruct(id: "", namespace_id: "", universal_id: "", universal_id_type: "") | |
# @type t :: %HL7.Composite.EI{id :: binary, namespace_id :: binary, universal_id :: binary, universal_id_type :: binary} | |
def(keys()) do | |
{{:id, :binary}, {:namespace_id, :binary}, {:universal_id, :binary}, {:universal_id_type, :binary}} | |
end | |
def(valid?(%HL7.Composite.EI{})) do | |
true | |
end | |
def(valid?(_)) do | |
false | |
end | |
def(from_item(item)) do | |
HL7.Composite.Def.from_item(%HL7.Composite.EI{}, keys(), item) | |
end | |
def(to_iodata(map, options \\ [])) do | |
HL7.Composite.Def.to_iodata(map, keys(), options) | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment