Passing function result to macro

瀏覽次數:570 次
跳到第一則未讀訊息

Jeremy Huffman

未讀,
2013年3月4日 晚上10:51:132013/3/4
收件者:elixir-l...@googlegroups.com
Hi,

I have an issue I've been struggling with in using a particular macro. I've created a fairly simple example that shows the problem here: https://gist.github.com/jeremyjh/5087808

What is happening is I have a macro that accepts 3 arguments which it uses to define a function. It takes the name of the function, a keyword list for options, and then a body, which should be a do: Keyword.

What I am trying to do that is not working is that I want to specify another function to provide the options keywords, because I will pass these same options to the macro for several different functions. The reason it fails is because the macro I am calling tries to append the options to the body before entering any quoted block; this fails because at this point the "options" is a tuple containing my function name, not the options themselves (you can see this in the inspect output).


It seems like I what I want to be able to do is force my function to expand/evaluate before it tries to evaluate the macro. Changing the macro is not desirable because it could be (as it is in this case) that it is not my code. Is there anyway to do this?

Thanks,

Jeremy


José Valim

未讀,
2013年3月4日 晚上10:59:172013/3/4
收件者:elixir-l...@googlegroups.com
I cannot think of anything from the top of my mind that can make it work without changing the macro.

By the way, this example is exactly why it is recommended to avoid doing work inside of a macro that is not inside a quote, since it means the work is being done with expressions and not actual values.

In case you can change the macro, there are a couple options. The easier one would probably be to call Macro.expand passing each argument. Then you could either make give_it a macro or an attribute @give_it.


José Valim
Skype: jv.ptec
Founder and Lead Developer


--
You received this message because you are subscribed to the Google Groups "elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-co...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

Jeremy Huffman

未讀,
2013年3月4日 晚上11:12:572013/3/4
收件者:elixir-l...@googlegroups.com
I can make my example work if I move the append into a quoted block, but that is not easy to do in the actual macro. I tried Macro.expand but it doesn't seem to change anything; inspect margs is the same as inspecting args; see this new gist:

https://gist.github.com/jeremyjh/5087930

José Valim

未讀,
2013年3月4日 晚上11:16:222013/3/4
收件者:elixir-l...@googlegroups.com
Hrm, in your example, you should have used __CALLER__ instead of __ENV__. But even though, I did the change here and it did not work, that's because by the time the macro is expanded, the give_it macro (nor the @give_it variable) would be defined.

I guess the option is to stop relying on the macro time behaviour. If you post the actual macro here, we can try to give you a hand. :)



José Valim
Skype: jv.ptec
Founder and Lead Developer


Jeremy Huffman

未讀,
2013年3月4日 晚上11:28:582013/3/4
收件者:elixir-l...@googlegroups.com
The actual macro is part of Sasa's Exactor library.  https://github.com/sasa1977/exactor/blob/master/lib/exactor.ex

I'm making a call to defcall/3 - my code is here: https://gist.github.com/jeremyjh/5087987

I just want to be able to define a function or macro so I don't have to type export: :scheduler for every defcall I setup.

José Valim

未讀,
2013年3月5日 凌晨12:05:532013/3/5
收件者:elixir-l...@googlegroups.com
Thanks Jeremy!

For now, you will have to duplicate the options.

I will try to discuss with Sasa's how we can make Exactor library more "runtime friendly".
But a basic example is, instead of doing something like:


   defmacro defcast(cast, options, body) do
wrap_and_delegate(:defcast, cast, Keyword.from_enum(options ++ body ++ [module: __CALLER__.module]))
   end

Most of the work should be done inside a quote instead. Some arguments should be really handled as an AST expression, like the defcast name and the body. Those can be passed down to the quote with escaping:



   defmacro defcast(cast, options, body) do
quote do
 { name, args, guards, body } = unquote(__MODULE__).wrap_and_delegate(
unquote(Macro.escape cast),
unquote(options),
unquote(Macro.escape body)
)

# atom, quoted list, quoted list, quoted term
def name, args, guards, do: body
end
   end

In the example above, the options will still be evaluated in the environment it was defined. Following this approach will allow the @give_it(args) approach to work or even a regular variable. The macro is not going to work anyway because, at the time wrap_and_delegate is called, the macro is not defined yet.

José Valim
Skype: jv.ptec
Founder and Lead Developer

Jeremy Huffman

未讀,
2013年3月5日 凌晨12:18:262013/3/5
收件者:elixir-l...@googlegroups.com
Great, thanks for all the analysis on this little issue - I just couldn't leave it alone if there was a chance there is something I missed that I could do in the macro invocation. This has really improved my understanding of the macro system.

Regards,

Jeremy

Saša Jurić

未讀,
2013年3月5日 下午1:18:192013/3/5
收件者:elixir-l...@googlegroups.com
Hi Jeremy,

First of all I am glad to hear that somebody else besides me uses exactor :-)

I am also still learning Elixir, and find new details and aspects about macros every day.
Your problem seems like something others might find useful, so I'm willing to modify exactor to support it.

Maybe something like this would be ok?

defmodule MyActor do
  use ExActor, export: :my_actor
  
  ...
end

In addition to avoiding repetition, this would also eliminate the need to register the process manually, since the start_xxx functions would also be modified to register the process automatically.

Alternatively (or in addition), I could modify the code as Jose suggests which should enable it to work with module attributes. The usage would be a bit uglier:

defmodule ... do
  @my_defaults [...]
  defcast myop, [@my_defaults | ...], do: ...
end

However, this requires no change to the exactor interface, and also supports greater flexibility. For example, you can have multiple defaults, and select which one you want for each defcall/cast
I'm interested in opinion of others, which one do you prefer?

Sasa

Jeremy Huffman

未讀,
2013年3月5日 下午1:32:062013/3/5
收件者:elixir-l...@googlegroups.com

Hi Sasa,

Thanks, and thanks for writing this library - I like the interface it provides. I think your first suggestion is best for my case and other cases where people basically have a "singleton" actor. Having the other mechanism  would also be nice but the extra flexibility would mean I still have a bit more repetition in my current use case - as compared to the first option.

Sasa Juric

未讀,
2013年3月5日 下午3:03:362013/3/5
收件者:elixir-l...@googlegroups.com
Hi Jeremy,

I added the support for module level export definitions, check it out on the github and let me know if anything else is needed.

Sasa

Sasa Juric

未讀,
2013年3月5日 下午4:46:082013/3/5
收件者:elixir-l...@googlegroups.com

As an aside, to address your original question, If you wanted to do this via macros, I think the cleanest approach is to write your own macros which wrap original macro

For example, exactor used to do this and rely on genx implementation:

Obviously, in your case, it was better to extend the library, but when this is not possible or easy, you can always write your own macros to remove the duplication.

Jeremy Huffman

未讀,
2013年3月5日 晚上8:58:262013/3/5
收件者:elixir-l...@googlegroups.com
Hi, awesome, works great. Thanks so much!


Jeremy Huffman

未讀,
2013年3月5日 晚上9:01:142013/3/5
收件者:elixir-l...@googlegroups.com
Ok, thank you yes its handy to know that. I tried a sort of naive version of this but didn't get it anywhere near working so the example is really good to have.


--
回覆所有人
回覆作者
轉寄
0 則新訊息