One of the widely discussed features of Julia is its support for metaprogramming. This feature makes it possible to generate and inspect the code at runtime. In essence, metaprogramming blurs the distinction between code and data. When used carefully, it can contribute to good code.
Of course, Julia is not the only or the first language to support Metaprogramming. Many other languages such as Lisp, Clojure, Ruby, Elixir, and Python also have support for metaprogramming.
In today’s article, I would like to show how we can define functions at runtime in Julia. This technique applies to generating any code, although my focus is on defining functions.
1) Generating using Strings
This is the simplest way to generate runtime code. We use the “Meta.parse()” function for this. The following example shows how it is done:
The function “ToFunction()” takes a String argument and converts that to a Julia function. Cell[2] shows how this works. As cell[4] shows, the function can be invoked using its defined name.
Here is another example that defines an “anonymous” function. The behaviour is the same.
Some programmers might prefer to define “ToFunction()” as a “macro” instead of as a regular function. This permits the function synthesis to happen in two stages: the AST is generated at “compile time” and is turned into a function at runtime.
We can tweak our function slightly so that it accepts multiple arguments:
Needless to say, the supplied String argument must represent a valid function definition. If not, we will get an error:
2) Generating using AST
Instead of using a String representation of the function to be defined, it is possible to work directly at the level of AST. This is essentially how “macros” work. In the following example, the macro “MakeFunction()” takes an arbitrary “Expr” object as its argument and embeds it inside the body of the function to be synthesized.
Cell[21] shows how this macro is invoked. Note that the generated function is “anonymous” and hence must be called using the returned function object. Cells[22] and [23] show that the function works as expected.
Here is another example that passes an expression involving a variable in the current scope:
As cells[31] and [32] show, this also works correctly.
There is a useful function called “methods()” that returns the method table for any given function. We can use it in our case to learn about the “dynamic” method that we synthesized:
The examples discussed above only “scratch the surface” as far as metaprogramming in Julia is concerned. I encourage you to visit the documentation to learn more about this topic.
You can download the JupyterLab notebook used in this article.
Have a great weekend!
Recent Comments