Julia中,Lisp留下的最好的禮物就是metaprogramming。Julia體現了Lisp "code as a data structure"的精神
每個Julia程式都是生於字串
prog = "1 + 1"  # "1 + 1"
接著,字串會被解析,成為叫作Expr的物件
ex1 = parse(prog)  # :(1 + 1)
typeof(ex1)  # Expr
Expr包含了3個部份:
head:以一個Symbol來表示這個Expr的種類,而Symbol你可以把他當成電腦讀的懂的字串args:代表著expression的參數,你可以想成是這些元素組成這個expression的typ:表示這個Expr他回傳值的型別ex1.head  # :call
ex1.args  # [:+, 1, 1]
ex1.typ  # Any
Expr也可以被直接建構,運算子會以prefix的方式建構,像以下這個樣子
ex2 = Expr(:call, :+, 1, 1)
ex1 == ex2  # true
dump(ex2)  # 這可以把Expr的資訊印出來
Expr
  head: Symbol call
  args: Array{Any}((3,))
    1: Symbol +
    2: Int64 1
    3: Int64 1
  typ: Any
要宣告Symbol很簡單,只要加個:就好
:foo  # :foo
typeof(:foo)  # Symbol
:foo == Symbol("foo")  # true
要宣告Expr也很簡單,只要用:()將他包住就可以了
ex = :(a + b * c + 1)  # :(a + b * c + 1)
大家可以看看他的結構,會發現ex.args的第3個是個Expr,Expr還可以展開成其他東西
Julia的語法解析會這樣一層一層解析下去
dump(ex)
Expr
  head: Symbol call
  args: Array{Any}((4,))
    1: Symbol +
    2: Symbol a
    3: Expr
      head: Symbol call
      args: Array{Any}((3,))
        1: Symbol *
        2: Symbol b
        3: Symbol c
      typ: Any
    4: Int64 1
  typ: Any
另一種宣告Expr的方法
ex = quote
    x = 1
    y = 2
    x + y
end
quote
    x = 1
    y = 2
    x + y
end
Julia語言自己可以解析自己是個強大的功能,但是更強大的在後面
a = 1;
ex = :($a + b)  # :(1 + b)
這邊可以讓作為變數的a直接將值帶入,如此一來就可以直接做推斷
ex = :(a in $:((1, 2, 3)) )  # :(a in (1,2,3))
接著,解析完之後就是執行了!!
eval(:(1 + 2))  # 3
eval()會直接執行Expr,他會直接取用global variable,更可能會更改到他們
ex = :(x = 1)
x  # LoadError: UndefVarError: x not defined
這段程式當中只有建構Expr,x沒有被宣告
eval(ex)
x  # 1
執行之後,x就有值了
應用以上的原理,macro就是這麼一個東西,他像一個函式一樣可以接受參數,他會回傳編譯過的Expr並執行
macro sayhello()
    return :( println("Hello, world!") )
end
@sayhello
Hello, world!
@sayhello這個macro將println("Hello, world!")這段程式碼編譯後,呼叫@sayhello就會直接執行了
macro sayhello(name)
   return :( println("Hello, ", $name) )
end
@sayhello("Bob")
@sayhello "Bob"
Hello, Bob
Hello, Bob
參數有以上兩種給法,可以不用括弧
今天介紹了Julia的macro這個強大的工具,他其在在Lisp, C等等語言中也被應用的,Julia裏面有很多功能都被寫成macro的形式。