Skip to content
On this page

What is meta-programming

This introduction is aimed for people who are new to Julia and meta-programming. If you are already familiar with Julia and meta-programming, you can skip this section. If you are new to Julia, you can read the Julia documentation to learn more about the language.

Meta-programming is a programming technique in which computer programs have the ability to treat programs as their data. In other words, a part of the program is able to read and manipulate other parts of itself while it is running.

Julia has a powerful meta-programming system that allows you to write Julia functions that generate other Julia program. This is a very powerful technique that can be used to implement many language features, such as reflection, and code generation. It is also a powerful tool for building domain-specific languages (DSLs).

Why use Expronicon?

The Expronicon package provides a set of tools for working with Julia expressions. It gives you the ability to build, inspect, and manipulate Julia expressions. It also provides a set of macros that make it easy to work with expressions. It makes your code more readable and easier to maintain.

Meta-programming in Julia

Julia's meta-programming system is based on the idea of expressions. An expression is a piece of code that can be evaluated to produce a value. For example, the expression 1 + 2 evaluates to the value 3. Expressions can be nested, so 1 + 2 * 3 evaluates to 7.

Expressions are represented in Julia by the Expr type. You can create an expression using the quoting syntax:

julia
julia> :(1 + 2)
    :(1 + 2)

The :(...) syntax is called a quotation and it creates an expression. The expression :(1 + 2) is equivalent to the expression Expr(:call, :+, 1, 2). The Expr constructor takes a head and zero or more arguments. The head is a symbol that describes the type of expression. The arguments are the values that the expression operates on. For example, the expression 1 + 2 has the head :call and the arguments :+ and 1 and 2.

You can use the dump function to see the structure of an expression:

julia
julia> dump(:(1 + 2))
Expr
  head: Symbol call
  args: Array{Any}((3,))
    1: Symbol +
    2: Int64 1
    3: Int64 2

The dump function prints the type of the expression, the head, and the arguments. The arguments are printed recursively, so you can see the structure of nested expressions.

Evaluating expressions

Although, this is usually not recommended inside a function definition, you can use the eval function to evaluate an expression:

julia
julia> eval(:(1 + 1))
2

or you can use the @eval macro:

julia
julia> @eval 1 + 1
2

The @eval macro is equivalent to eval(:(...)). The @eval macro is useful when you want to evaluate an expression that contains variables:

julia
julia> a = 1
1

julia> b = 2

julia> @eval a + b
3

The @eval macro is useful when you want to generate code during precompilation of a package. For example, the @eval macro is used in the Yao.jl to generate the mat function for each gate type:

julia
for T in [:(RotationGate{D,<:SymReal} where D), :(PhaseGate{<:SymReal}), :(ShiftGate{<:SymReal})]
    @eval YaoBlocks.mat(gate::$T) = mat(Basic, gate)
end

Building expressions

You can use the Expr constructor to build expressions:

julia
julia> Expr(:call, :+, 1, 2)
:(1 + 2)

The Expr constructor takes a head and zero or more arguments. The head is a symbol that describes the type of expression. The arguments are the values that the expression operates on. For example, the expression 1 + 2 has the head :call and the arguments :+ and 1 and 2.

Or you can use interpolation to build expressions:

julia
julia> :(1 + $(2 * 3))
:(1 + 6)

The $(...) syntax is called an interpolation. It evaluates the expression inside the parentheses and inserts the result into the expression. The $(2 * 3) expression is evaluated to the value 6, which is inserted into the expression :(1 + 6).

Defining macros

macro is a special Julia function that is executed at compile time. It takes an expression as an argument and returns an expression. For example, the following macro takes an expression and returns an expression that evaluates to the same value:

julia
macro identity(ex)
    return ex
end

You can inspect the returned expression using the @macroexpand macro:

julia
julia> @macroexpand @identity 1 + 2
:(1 + 2)

The @macroexpand macro is useful for debugging macros. It prints the expression that will be evaluated instead of the expression that was passed to the macro.

You can use the @macroexpand macro to see how the @eval macro is implemented:

julia
julia> @macroexpand @eval 1 + 1
:(Core.eval(Main, $(Expr(:copyast, :($(QuoteNode(:(1 + 1))))))))

Learn more about expressions

For more information about Julia expression itself, see the Meta Programming section of the Julia manual.

Released under the MIT License.