YouTip LogoYouTip

Julia Metaprogramming

Julia represents its own code as data structures in the language, allowing us to write programs that manipulate programs. Metaprogramming can also be simply understood as writing code that generates code. > **Metaprogramming** is the writing of computer programs that write or manipulate other programs (or themselves) as their data, or that do part of the work at compile time that would otherwise be done at runtime. In many cases, this allows programmers to be more productive than they could be by writing code manually, or gives programs greater flexibility to handle new situations without recompilation. > > The language used to write metaprograms is called the **meta language**. The language of the programs being manipulated is called the **target language**. A programming language's ability to be its own meta language is known as **reflection** or **reflexivity**. > > -- Wikipedia ### Julia Source Code Execution Phases **1. Parse raw Julia code**: Julia's parser first parses the string to obtain an Abstract Syntax Tree (AST), which is a structure that contains all code in an easy-to-manipulate format. **2. Execute parsed Julia code**: In this stage, the parsed Julia code is executed. When we enter code in an interactive programming environment (REPL) and press Enter, both of these phases are executed. Using metaprogramming tools, we can access the Julia code between these two phases, i.e., after source code parsing but before execution. ## Program Representation Julia provides a Meta class, where Meta.parse(str) can be used to parse a string, and typeof(e1) returns Expr: ## Example julia> prog = "1 + 1" "1 + 1" julia> ex1 = Meta.parse(prog) :(1 + 1) julia> typeof(ex1) Expr Returns **:(1 + 1)**, this returned value consists of a colon followed by the expression, and typeof(ex1) returns Expr. An Expr object contains two parts (ex1 has head and args properties): A symbol object that identifies the expression type. ## Example julia> ex1.head :call The other is the arguments of the expression, which can be symbols, other expressions, or literals: ## Example julia> ex1.args 3-element Vector{Any}: :+ 1 1 Expressions can also be constructed directly using Expr: ## Example julia> ex2 = Expr(:call, :+, 1, 1) :(1 + 1) The two expressions constructed above: one through parsing and one through direct construction, they are equivalent: ## Example julia> ex1 == ex2 true Expr objects can also be nested: ## Example julia> ex3 = Meta.parse("(4 + 4) / 2") :((4 + 4) / 2) We can also use Meta.show_sexpr to view expressions. The following example shows nested Expr: ## Example julia> Meta.show_sexpr(ex3) (:call, :/, (:call, :+, 4, 4), 2) ### Symbols We can store an unevaluated but parsed expression using the colon **:** prefix operator. ## Example julia> ABC = 100 100 julia> :ABC :ABC Quote the entire expression below: ## Example julia> :(100-50) :(100 - 50) Quote an arithmetic expression: ## Example julia> ex = :(a+b*c+1) :(a + b * c + 1) julia> typeof(ex) Expr Note that equivalent expressions can also be constructed using Meta.parse or directly using Expr: ## Example julia>:(a + b*c + 1) == Meta.parse("a + b*c + 1") == Expr(:call, :+, :a, Expr(:call, :*, :b, :c), 1) true Multiple expressions can also be contained in a code block using **quote ... end**. ## Example julia> ex = quote x = 1 y = 2 x + y end quote #= none:2 =# x = 1 #= none:3 =# y = 2 #= none:4 =# x + y end julia> typeof(ex) Expr ## Executing Expressions After an expression is parsed, we can use the eval() function to execute it: ## Example julia> ex1 = :(1 + 2) :(1 + 2) julia>eval(ex1) 3 julia> ex = :(a + b) :(a + b) julia>eval(ex) ERROR: UndefVarError: b not defined [...] julia> a = 1; b = 2; julia>eval(ex) 3 ## Abstract Syntax Tree (AST) An Abstract Syntax Tree (AST) is a structure that represents an abstract view of the syntax structure of source code. It presents the syntax structure of a programming language in a tree form, where each node in the tree represents a structure in the source code. We can view the hierarchical structure of an expression using the dump() function: ## Example julia> dump(:(1* cos(pi/2))) Expr head: Symbol call args: Array{Any}((3,)) 1: Symbol* 2: Int64 1 3: Expr head: Symbol call args: Array{Any}((2,)) 1: Symbol cos 2: Expr head: Symbol call args: Array{Any}((3,)) 1: Symbol / 2: Symbol pi 3: Int64
← Ref Random RandintRef Math Remainder β†’