Create function on-the-fly?

Let say I have a tuple :

{:sum, %{x: 23}, %{y: 55 }}

Out of this how can I create the following function :

def sum({ %{x: 23}, %{y: 55 }} ), do: true

as a second question, let say I have :

{:sum, %{x: 23}, %{y: 55 }, %{when: “y > 11”} }

how do I create the function with guard ? btw. guard format could be different, say list or something else . whichever is easier ?


How about the case where the function that has to be generated has the form :

def sum({ work = %{x: 23, y: 54 } }) …

Can you talk a bit more about your use case?

Elixir macros can generate functions at compile time from data but this is a bit of an odd way to go about it. If you’re trying to do it at run time I think you really just need to treat it as a data transformation instead of as meta-programming.

2 Likes

I’m trying to make sort of Production system and want header-function-matching to do most of the work for me, where the rules are passed/created from tuples.

Btw … prod system is a list of IF-THEN rules and perfectly match “def fun(IF), do: THEN pattern”

Here’s a simple example using your data that might get you started:

defmodule Gen do
  @fun_list [
    {:sum, %{x: 23}, %{y: 55 }}
  ]
  
  for {fun, a, b} <- @fun_list do
    def unquote(fun)(unquote(Macro.escape(a)), unquote(Macro.escape(b))), do: true
  end
end

In IEx:

iex(3)> Gen.sum(%{x: 23}, %{y: 55 })                                                      
true
5 Likes

Why this does not work, but the one you gave work.

defmacro rule({ name, goal, work }) do
    quote do
         def unquote(name)({ unquote(Macro.escape(goal)), unquote(Macro.escape(work)) }), do: true
    end
end

ProdSys.rule({ :test, %{ goal: “test” }, %{} })

** (ArgumentError) cannot invoke def/2 outside module
(elixir) lib/kernel.ex:5122: Kernel.assert_module_scope/3
(elixir) lib/kernel.ex:3893: Kernel.define/4
(elixir) expanding macro: Kernel.def/2
iex:24: (file)
(prod_sys) expanding macro: ProdSys.rule/1
iex:24: (file)

iex(28)> expr = quote do: ProdSys.rule({ :test, %{ goal: "test" }, %{} })
  {{:., [], [{:__aliases__, [alias: false], [:ProdSys]}, :rule]}, [],
  [{:{}, [], [:test, {:%{}, [], [goal: "test"]}, {:%{}, [], []}]}]}


iex(29)> res = Macro.expand_once(expr, __ENV__)                          
 {:def, [context: ProdSys, import: Kernel],
 [
   {:{}, [context: ProdSys],
    [
      {[],
        [:test, {:{}, [], [:%{}, [], [goal: "test"]]}, {:{}, [], [:%{}, [], []]}]}
     ]},
    [do: true]
 ]}


iex(30)> IO.puts Macro.to_string(res)                                    
 def({{[], [:test, {:%{}, [], [goal: "test"]}, {:%{}, [], []}]}}) do
  true
 end
:ok

What error message do you get and what is the full example code to test with?

1 Like

here is simpler one :

iex(40)> expr = quote do: ProdSys.rule({ :test, 3,5 })
 {{:., [], [{:__aliases__, [alias: false], [:ProdSys]}, :rule]}, [],
  [{:{}, [], [:test, 3, 5]}]}

iex(41)> res = Macro.expand_once(expr, __ENV__)                          
 {:def, [context: ProdSys, import: Kernel],
 [{:{}, [context: ProdSys], [{[], [:test, 3, 5]}]}, [do: true]]}

iex(42)> IO.puts Macro.to_string(res)                 
  def({{[], [:test, 3, 5]}}) do
   true
 end

Here is another example :

 defmodule Test do

defmacro rule(namex) do
	quote do
		def unquote(namex)() do
		 true
		end
	end
end

@lst [
	{:sum, 5, 7 },
	{:sum, 1, 3}
]

for { name, goal, work } <- @lst do
	def unquote(name)({ unquote(Macro.escape(goal)), unquote(Macro.escape(work)) }), do: true
end
end 



iex(8)> Test.rule(:test) 
** (ArgumentError) cannot invoke def/2 outside module
(elixir) lib/kernel.ex:5122: Kernel.assert_module_scope/3
(elixir) lib/kernel.ex:3893: Kernel.define/4
(elixir) expanding macro: Kernel.def/2
iex:8: (file)
(prod_sys) expanding macro: Test.rule/1
iex:8: (file)

iex(8)> Test.sum({5,7}) 
true

iex(9)> Test.sum({1,3})  
true

That tells you exactly the issue, you are calling rule/1, which is a macro that returns a def AST, and you cannot call def at that location, you can only call it from inside a module. :slight_smile:

So how can I solve it… i.e. be able to create functions on demand !?

Exactly what the error says, you have to call your rule macro from inside a defmodule body. :slight_smile:

hmmm… that is the idea to use it from outside.

Also exploring Prolog. There it also seems impossible to just reuse the rules, but may be I can cheat in different way.

Also looked of paper of ERESYE written in Erlang, they seem to be doing something along the lines what I’m trying.
http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.73.4327&rep=rep1&type=pdf

As a side note, if you want a prolog’y thing on the beam there is erlog. ^.^

What do you mean ‘outside’? On the beam all things, code or not everywhere at any time exist compiled in a module (even in the shell you are running is just inside a module except it even interprets what you are doing, so you could make an anonymous function for example, but that anonymous function is bound to that module too).

Yeah I know about erlog , I used it in the past. thanks