Skip to content

Instantly share code, notes, and snippets.

@jcomellas
Created May 15, 2015 01:13
Show Gist options
  • Save jcomellas/ee5cd5b7963dc6b79282 to your computer and use it in GitHub Desktop.
Save jcomellas/ee5cd5b7963dc6b79282 to your computer and use it in GitHub Desktop.
Elixir macro that dynamically adds type specs
# 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