Julia: Defining Functions Dynamically

Written by on March 5, 2023 in Julia, Programming with 0 Comments

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:

Defining A Function Dynamically

Defining A Function Dynamically

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.

Defining Anonymous Function Dynamically

Defining Anonymous Function Dynamically

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.

Defining A Macro Instead of Function

Defining A Macro Instead of Function

We can tweak our function slightly so that it accepts multiple arguments:

Refining the Macro

Refining the Macro

Needless to say, the supplied String argument must represent a valid function definition. If not, we will get an error:

Function Definition Error

Function Definition 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.

Synthesis Using AST

Synthesis Using AST

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:

Using a Variable in the Expression

Using a Variable in the Expression

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 Methods() Function

The Methods() Function

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!

Tags: ,

Subscribe

If you enjoyed this article, subscribe now to receive more just like it.

Subscribe via RSS Feed

Leave a Reply

Your email address will not be published. Required fields are marked *

Top