of information needed to embed other DSLs • Allows you to edit entire codebase in one fell swoop • Plumbing data structures via normal functions is painful • Code base begins to solidify and data structures allow very generic pipelines
need functions and data, and should not create DSLs too early • Wait until data structures are somewhat stable before creating DSLs • Wait until plumbing is painful/buggy or some compile time need for macros exists before thinking about DSL • You probably do not need a DSL
attributes at compile time • Embed into function calls / data structures • Put macro data into internal data structure, call Module.put_attribute/3 after validation of opts
in Ecto • Encode data into functions • Use behaviours for compile/refactor safety • Ask a module questions by passing around module name and invoking functions for use in generic pipelines
work in macros and delegate out to functions ASAP • Use location: :keep to get better line numbers in errors • Validate inputs (especially unnecessary opts) • Use after_compile hooks to validate state where appropriate • Avoid dynamic name generation, optimize for greppability • Disabling lexical tracker useful for preventing deadlocks
1] table :example do description "This is an example table" attribute :foo, :integer, required?: true, description: "foos do foo things" attribute :bar, :string, default: "none" end end
do quote location: :keep do # ... @ectoo_table unquote(name) import Ectoo.Table try do unquote(body) after _ -> :ok end import Ectoo.Table, only: [] # ... end end # ... end
description) do quote location: :keep do Ectoo.Table.__description__(__MODULE__, unquote(description)) end end def __description__(module, description) do Module.put_attribute(module, :ectoo_description, description) end # ... end
do quote location: :keep do # ... use Ecto.Schema schema @ectoo_table do for field <- @ectoo_attributes, do: field field.name, field.type, default: field.default for relationship = %{cardinality: :belongs_to} <- @ectoo_relationships, do: belongs_to relationship.name, relationship.related for relationship = %{cardinality: :has_many} <- @ectoo_relationships do: has_many relationship.name, relationship.related for relationship = %{cardinality: :has_one} <- @ectoo_relationships, do: has_one relationship.name, relationship.related end # ... end end # ... end
1] table :example do description "This is an example table" attribute :foo, :integer, required?: true, description: "foos do foo things" attribute :bar, :string, default: "none" end end
to have all Ecto options • Build helpers to answer common questions from data • Use the data structures to build generic pipelines, e.g. Changesets • Add more macros to enable more sophisticated pipelines