I’ve been building a tool that compiles elixir expressions into a “workflow” a directed acyclic graph of steps, conditions, and other nodes. Some examples in a livebook here. The advantage of doing this is it allows for composition of functionality at runtime - with varying levels of potential user defined logic. Should be handy for data pipelines, rule based expert systems or other runtime dependent workflows.
A big part of expert systems is to build rules at runtime, but to do this I need to be able to inject runtime values into a macro (at least as implemented - if there’s a better way to extract atomic conditionals in a function I’m all ears).
So the idea is supporting ^
as an escape value like Ecto and Explorer do, but I barely understand Macros and have been struggling to figure out how to access the runtime values in this situation.
test "escapes runtime values with '^'" do
some_values = [:potato, :ham, :tomato]
escaped_rule = Dagger.rule(
name: "escaped_rule",
condition: fn val when val in ^some_values -> true end,
reaction: "food"
)
assert match?(%Rule{}, escaped_rule)
assert Rule.check(escaped_rule, :potato)
end
I want the left hand side of this rule to build a function such as:
fn val when val in [:potato, :ham, :tomato] -> true end
I noticed Jose implemented something similar in Explorer recently: explorer/query.ex at v0.5.5 · elixir-nx/explorer · GitHub but I don’t quite understand how it works yet.
I’ve tried a few variations such as:
check =
Macro.prewalk(lhs, fn
{:^, _meta, [{bind, _, _} = expr]} = ast ->
Macro.Env.has_var?(context, {bind, nil}) |> IO.inspect(label: "has var?") # true
runtime_value = Macro.Env.vars(context) |> IO.inspect() # [some_values: nil]
if runtime_value do
var = Macro.unique_var(bind, __MODULE__)
quote do: unquote(var)
else
ast
end
ast ->
ast
end)
Can anyone more familiar with macros and/or escaping runtime values help me understand how this works or how you’d approach this problem? Any feedback is appreciated.
Thanks.