Firstly I should cite one paragraph of presentation copied from Julia official doc:
"The strongest legacy of Lisp in the Julia language is its metaprogramming support. Like Lisp, Julia represents its own code as a data structure of the language itself. Since code is represented by objects that can be created and manipulated from within the language, it is possible for a program to transform and generate its own code. This allows sophisticated code generation without extra build steps, and also allows true Lisp-style macros operating at the level of abstract syntax trees(AST). In contrast, preprocessor "macro" systems, like that of C and C++, perform textual manipulation and substitution before any actual parsing or interpretation occurs. Because all data types and code in Julia are represented by Julia data structures, powerful reflection capabilities are available to explore the internals of a program and its types just like any other data."
As far as I understand today, code written in Julia would be firstly parsed by compiler as Expr, Symbol and QuoteNode that are structured as ASTs. Compiler then executes them in the same way how function
eval() works. In this point of view, everything in Julia is Expression.
Below are my learning notes.
ex = :(a + b) # define an expression as :(expression) println("ex: ", typeof(ex)) # Expr x = :(ab) # see what it gives if there is no operator or functions in expression println("x: ", typeof(x)) # Symbol println(ex.head, ", ", ex.args) println("dump(ex): ") dump(ex) ## equivalent to Meta.@dump :expr # to define multiple-line expression, one should use: quote ... end ex1 = quote a + b end println("ex1: ", typeof(ex1))
Expression can be parsed from a string (as our code in src files or typed in terminal are all strings):
ex = :(a + b) expr_str = "a + b" ex2 = Meta.parse(expr_str) println("ex2: ", typeof(ex2)) println("ex == ex2?? answer is : ", ex == ex2) println("dump(ex2): ") dump(ex2)
It's also possible to define expression by using
ex = :(a + b) ex3 = Expr(:call, :+, :a::Symbol, :b::Symbol) println("ex == ex3?? answer is : ", ex == ex3) println("dump(ex3): ") dump(ex3)
We can use dollar symbol '$' to refer to content of a variable, same as in Linux terminal.
a = 1; ex4 = :($a + b) # $a yields 1, however if a is not yet defined, there will be an error println("dump(ex4): ") dump(ex4)
With all the stuffs presented above, now it is easy to understand macro in Julia. I try to define a macro named sayhello with a "name" variable as input. Let's see how it works.
macro sayhello(name) return :( println("Hello, ", $name) ) end @sayhello("jemaloQ") #= We can view the quoted return expression using the function macroexpand (important note: this is an extremely useful tool for debugging macros): =# ex = macroexpand(Main, :(@sayhello("jemaloQ")) ) println("type of ex: ", typeof(ex), ", ex: ", ex) eval(ex)