I was toying around to see if I could possibly get something similar to Clojure-style pre/post enforcement. This is what I came up with and would like your input. Thanks.
Example Usage
defmodule Example do
require Contract
def reverse_abc(x) do
# Contract pre processing
pre_must_be_abc = fn -> x == "abc" end
pre = [
pre_must_be_abc,
]
# Contract post processing
post_must_be_cba = fn x -> x == "cba" end
post_must_be_string = fn x -> is_binary x end
post = [
post_must_be_cba,
post_must_be_string,
]
# Contract
# The arguments here are `[x]` and do not represent the current value of `x`
reverse_abc_contract = Contract.new([x], [pre: pre, post: post], String.reverse(x))
# Encorce Contract
reverse_abc_contract.(x)
end
end
Basic Implementation
defmodule Contract do
defmodule EnforcePreError do
defexception message: "Argument(s) Failed Pre Processing Requrements."
end
defmacro enforce(:pre, list, expr) do
quote do
valid? =
Enum.map(unquote(list), fn f -> f.() end)
|> Enum.all?(&(&1 == true))
case valid? do
true -> unquote(expr)
_ -> raise EnforcePreError
end
end
end
defmodule EnforcePostError do
defexception message: "Return value Failed Post Processing Requrements."
end
defmacro enforce(:post, list, value) do
quote do
valid? =
Enum.map(unquote(list), fn f -> f.(unquote(value)) end)
|> Enum.all?(&(&1 == true))
case valid? do
true -> unquote(value)
_ -> raise EnforcePostError
end
end
end
defmodule EnforceNothingError do
defexception message: "No enforcements are defined."
end
defmacro enforce(:prepost, pre, post, expr) do
quote do
pre = unquote(pre) || []
post = unquote(post) || []
no_enforcements? = (pre == [] && post == [])
get_value = fn ->
unquote(__MODULE__).enforce(:pre, unquote(pre), unquote(expr))
end
cond do
no_enforcements? -> raise EnforceNothingError
pre && post -> unquote(__MODULE__).enforce(:post, unquote(post), get_value.())
(pre == []) && post -> unquote(__MODULE__).enforce(:post, unquote(post), unquote(expr))
(post == []) && pre -> get_value.()
end
end
end
defmacro new(args, opts, expr) do
quote do
fn (unquote_splicing(args)) ->
unquote(__MODULE__).enforce(
:prepost, unquote(opts[:pre]), unquote(opts[:post]), unquote(expr)
)
end
end
end
end