Detecting constant forms

Consider this code:

defmodule MyMacro do

  defmacro my_macro(spec) do
    name = Keyword.get(spec, :name)

    quote do
      unquote(name)
    end
  end

end

require MyMacro

MyMacro.my_macro([name: "qwe"])
##"qwe"

spec = [name: "qwe"]
## [name: "qwe"]
iex(6)> MyMacro.my_macro(spec)         
## ** (FunctionClauseError) no function clause matching in Keyword.get/3
##    (elixir) lib/keyword.ex:150: Keyword.get({:spec, [line: 6], nil}, :name, nil)
###            expanding macro: MyMacro.my_macro/1
##             iex:6: (file)

error looks perfectly reasonable here. But it’s clear that spec variable is actually a constant form (maybe not conforming to strict definition of environment independence, but what is most important is ‘top-level’ list literal). So the question is how to detect constant forms in Elixir (and, how to get spec value AST)?

Mymacro.my_macro(spec) passes the AST of what is placed there in to arguments, hence why it becomes {:spec, [line: 6], nil}, that also means that spec only exists as AST and is not resolved to become the [name: "qwe"] until the macro returns and it is compiled in the original __ENV__. In macro’s you operate over AST’s, those AST’s do not mean anything (and as such you can modify the AST all you wish) until the macro returns the AST (or a new one) and it is finally resolved in the original parent __ENV__.

So creating variable is purely ‘AST-only’ thing unless byte-code conversion stage? It doesn’t produce any side-effects? To give you more context why I’m asking - in Common Lisp certain forms can alter macro expansion environment so this additional information can be used by other macros (see constantp for an example).

Two things here, first the token slug that is passed to your macro is only that, a token, it has no real associated information yet in your macro (in reality you can get some information ‘sometimes’ based on alias’s and such, but for variables there is no such information). Second, variables never produce side-effects, immutable language. :slight_smile:

As for altering the environment, you will need to return an AST that will be executed in the parent environment to do the altering that you want (which can indeed be ‘another’ macro, perhaps one that has the parent’s __ENV__ passed in to it so you can resolve things directly, *hint*hint*. ^.^).

Thank you for the discussion!

1 Like