Other languages generating code like Elixir does

metaprogramming
#1

One thing I really like in Elixir is the possibility to generate Elixir code in Elixir without using even macros.

Do you know of any other language with this same capability? For example, such language could be an interesting choice to create satellite nodes of an Elixir/Erlang system.

1 Like
#2
  • Ruby where you have for example test which is plain function that creates function.
  • Lisps, where code is data.
  • JavaScript where you can dynamically create structures and add methods to the prototypes. Io in the same manner.
  • AFAIK Python can do that (I have never worked with Python though), for examples see import future
  • Can we count JIT there? If yes, then almost all compiled languages can use form of the “on the fly” code generation.
  • Java (annotations) and C# - via reflection
  • C++ templates are Turing complete
  • D templates are quite powerful, however some can count that as a cheating, as these are almost macros in Elixir sense
  • Lua - metatables requires that

Your question is quite broad, as in theory, you can generate any code in any language (as long as both are Turing complete) and compile/interpret it as long as you are able to run compiler/interpreter, which you are able to do if you can run Turing complete code on that machine (just implement compiler/interpreter in language of your choice).

1 Like
#3

Thanks for your detailed response.

Indeed, any language can be used to generate code in any other language, if not by writting a big string to a file and invoking the compiler of the generated source.

But my question is which other languages do so as elegantly as Elixir does.

But let me reduce the scope a little: and which are not Lisps.

#4

“Elegance” is highly subjective. So still it is hard to tell. We can even argue that Ruby meta programming is “cleaner” as do not require quote/unquote magic. Or that D templates are much better solution, as it almost looks like “regular” code. Or that Rust attributes syntax is much cleaner for end-user together with explicit marks when you use macro (all macro invocations need to end with !).

Also even with Elixir macros you cannot change language syntax, what is possible in some other languages (like Racket or Rust, as long as it is parseable).

#5

So lets refine my question again.

What I mean by “elegance” is “not using strings”, but instead, using the syntax of the language to generate its code, like in this example

defmodule Example do
    kv = [foo: "called foo", bar: "called bar"]
    Enum.each kv, fn {k, v} ->
      def unquote(k)(), do: unquote(v)
    end
end

Note that we are not using macros here in the sense that we are not generating code inside a defmacro block.

Which other languages can generate their own code like this, and which are not Lisp?

1 Like
#6

def is a macro. The usage of unquote kinda give it away …

#7

Nice one, but consider that it is fully expanded to remove all macros, and then we get pure Elixir code that generates Elixir code, so I think my question still holds.

Or does the “pure Elixir” code the expansion results into, uses strings to build the generated code?

#8

What you get is a data structure representing elixir code that is then used to “generate” erlang code …

It’s like writing an interpreter for lisp in any language, I recon.

1 Like
#9

Ok, no strings, so its Elixir generating Erlang code …

See that I refined my question above to “Note that we are not using macros here in the sense that we are not generating code inside a defmacro block .”

#10

elixir compiler generates the code, not elixir itself. We are using macros even if they are not explicit as is the case with defmacro. defmodule, def, etc are all macros. elixir expands them at compile time and then elixir’s compiler translates them into erlang.

1 Like
#11

So the Elixir compiler takes the data structure you mentioned before, and traverses it to generate Erlang code? Is that so?

#12

That’s my understanding, yes. I used to think that elixir targeted a layer below that (core erlang), but it seems to just output erlang.

#13

If you look closely, you will see macro there though :wink:

But ok, lets see:

function Example() {
  let ret = {}
  const kv = {
    foo: "called foo",
    bar: "called bar"
  }

  for (let name in kv) {
    ret[name] = () => kv[name]
  }
  
  return ret
}

const example = Example()

console.log(example.foo())
console.log(example.bar())
class Example
  {foo: "called foo", bar: "called bar"}.each do |key, value|
    define_method(key) { value }
  end
end

puts Example.new().foo
puts Example.new().bar

Unfortunately I do not have time to write more examples right now.

#14

This “data structure” is called abstract syntax tree and it is exactly what quote will spit out. So this is tuple in form {element :: atom(), environment :: keyword(), arguments :: term()} (however I do not know how the root of the tree looks like, so maybe someone with more knowledge can say more.

#15

Thanks for your examples, they are enough to show what Javascript and Ruby can do with respect to code generation.

This seems very much like what Elixir can do.

Do you know if Rust, or any other statically typed language that can do the same, as you only gave examples in dynamic languages?

#16
1 Like
#17

Well Elixir may expand everything to a big data structure, the AST, but then the part of the code corresponding to

    kv = [foo: "called foo", bar: "called bar"]
    Enum.each kv, fn {k, v} ->
      ...
    end

must be executed some how so that it generates foo and bar.

This makes Elixir compiler behave somewhat differently to a typical compiler that picks an AST and visits its node to translate into the target code.

What does Elixir do differently?

#18

elixir evaluates what it sees first unless it’s a macro, and then builds an ast from it.

#19

So there is initially a macro expansion phase, then Elixir executes the whole output of this phase, then it picks the resulting AST, and does the normal visit to translate to Erlang code. Is it like this?

#20

Thanks for making me aware that Elixir is a lisp-2.