当前位置:  首页>> 技术小册>> Julia入门教程

Julia 把自己的代码表示为语言中的数据结构,这样我们就可以编写操纵程序的程序。

元编程也可以简单理解为编写可以生成代码的代码。

元编程(英语:Metaprogramming),是指某类计算机程序的编写,这类计算机程序编写或者操纵其它程序(或者自身)作为它们的资料,或者在编译时完成部分本应在运行时完成的工作。多数情况下,与手工编写全部代码相比,程序员可以获得更高的工作效率,或者给与程序更大的灵活度去处理新的情形而无需重新编译。

编写元程序的语言称之为元语言。被操纵的程序的语言称之为”目标语言”。一门编程语言同时也是自身的元语言的能力称之为”反射”或者”自反”。

Julia 源代码执行阶段

1、解析原始 Julia 代码: Julia 解析器首先将解析字符串以获得抽象语法树(AST),AST 是一种结构,它以易于操作的格式包含所有代码。

2、执行已解析的 Julia 代码:: 在这个阶段,执行已解析的 Julia 代码。

当我们在交互式编程环境(REPL)中输入代码并按回车键时,会执行以上两个阶段。

使用元编程工具,我们就可以访问这两个阶段之间的 Julia 代码,即在源代码解析之后,但在执行之前。

程序表示
Julia 提供了一个 Meta 类,其中可以用 Meta.parse(str) 来对一个字符串进行解析,用 typeof(e1) 返回 Expr:

实例

  1. julia> prog = "1 + 1"
  2. "1 + 1"
  3. julia> ex1 = Meta.parse(prog)
  4. :(1 + 1)
  5. julia> typeof(ex1)

Expr

返回 :(1 + 1) , 这个返回的值由一个冒号和后面的表达式组成,用 typeof(ex1) 返回 Expr。

Expr 对象包含两个部分(ex1 包含了 head 和 args 属性):

一个标识表达式类型的符号对象。

实例

  1. julia> ex1.head
  2. :call

另一个是表达式的参数,可能是符号、其他表达式或字面量:

实例

  1. julia> ex1.args
  2. 3-element Vector{Any}:
  3. :+
  4. 1
  5. 1

表达式也可能直接用 Expr 构造:

实例

  1. julia> ex2 = Expr(:call, :+, 1, 1)
  2. :(1 + 1)

上面构造的两个表达式:一个通过解析构造,一个通过直接构造,两个是等价的:

实例

  1. julia> ex1 == ex2
  2. true

Expr 对象也可以嵌套:

实例

  1. julia> ex3 = Meta.parse("(4 + 4) / 2")
  2. :((4 + 4) / 2)

我们也可以使用 Meta.show_sexpr 查看表达式,以下实例展示了嵌套的 Expr:

实例

  1. julia> Meta.show_sexpr(ex3)
  2. (:call, :/, (:call, :+, 4, 4), 2)

符号

我们可以通过冒号 : 前缀运算符存储一个未计算但已解析的表达式。

实例

  1. julia> ABC = 100
  2. 100
  3. julia> :ABC
  4. :ABC

引用下面的整个表达式:

实例

  1. julia> :(100-50)
  2. :(100 - 50)

引用算数表达式:

实例

  1. julia> ex = :(a+b*c+1)
  2. :(a + b * c + 1)
  3. julia> typeof(ex)
  4. Expr

注意等价的表达式也可以使用 Meta.parse 或者直接用 Expr 构造:

实例

  1. julia> :(a + b*c + 1) ==
  2. Meta.parse("a + b*c + 1") ==
  3. Expr(:call, :+, :a, Expr(:call, :*, :b, :c), 1)
  4. true

引用多个表达式也可以在 quote … end 中包含代码块。

实例

  1. julia> ex = quote
  2. x = 1
  3. y = 2
  4. x + y
  5. end
  6. quote
  7. #= none:2 =#
  8. x = 1
  9. #= none:3 =#
  10. y = 2
  11. #= none:4 =#
  12. x + y
  13. end
  14. julia> typeof(ex)
  15. Expr

执行表达式

表达式解析后,我们可以使用 eval() 函数来执行:

实例

  1. julia> ex1 = :(1 + 2)
  2. :(1 + 2)
  3. julia> eval(ex1)
  4. 3
  5. julia> ex = :(a + b)
  6. :(a + b)
  7. julia> eval(ex)
  8. ERROR: UndefVarError: b not defined
  9. [...]
  10. julia> a = 1; b = 2;
  11. julia> eval(ex)
  12. 3

抽象语法树 (AST)

抽象语法树 (AST) 是一种结构,是源代码语法结构的一种抽象表示。

它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。

我们可以在 dump() 函数查看表达式的层次结构:

实例

  1. julia> dump(:(1 * cos(pi/2)))
  2. Expr
  3. head: Symbol call
  4. args: Array{Any}((3,))
  5. 1: Symbol *
  6. 2: Int64 1
  7. 3: Expr
  8. head: Symbol call
  9. args: Array{Any}((2,))
  10. 1: Symbol cos
  11. 2: Expr
  12. head: Symbol call
  13. args: Array{Any}((3,))
  14. 1: Symbol /
  15. 2: Symbol pi
  16. 3: Int64 2

插值

使用值参数直接构造 Expr 对象虽然很强大,但与 Julia 语法相比,Expr 构造函数可能让人觉得乏味。作为替代方法,Julia 允许将字面量或表达式插入到被引用的表达式中。表达式插值由前缀 $ 表示。

在此示例中,插入了变量 a 的值:

实例

  1. julia> a = 1;
  2. julia> ex = :($a + b)
  3. :(1 + b)

对未被引用的表达式进行插值是不支持的,这会导致编译期错误:

  1. julia> $a + b
  2. ERROR: syntax: "$" expression outside quote

在此示例中,元组 (1,2,3) 作为表达式插入到条件测试中:

  1. julia> ex = :(a in $:((1,2,3)) )
  2. :(a in (1, 2, 3))

在表达式插值中使用 $ 是有意让人联想到字符串插值和命令插值。表达式插值使得复杂 Julia 表达式的程序化构造变得方便和易读。

宏提供了一种机制,可以将生成的代码包含在程序的最终主体中。 宏将一组参数映射到返回的 表达式,并且生成的表达式被直接编译,而不需要运行时 eval 调用。 宏参数可能包括表达式、字面量和符号。

这是一个非常简单的宏:

实例

  1. julia> macro sayhello()
  2. return :( println("Hello, world!") )
  3. end
  4. @sayhello (macro with 1 method)

宏在Julia的语法中有一个专门的字符 @ (at-sign),紧接着是其使用 macro NAME … end 形式来声明的唯一的宏名。在这个例子中,编译器会把所有的 @sayhello 替换成:

  1. :( println("Hello, world!") )

当 @sayhello 在REPL中被输入时,解释器立即执行,因此我们只会看到计算后的结果:

  1. julia> @sayhello()
  2. Hello, world!

现在,考虑一个稍微复杂一点的宏:

实例

  1. julia> macro sayhello(name)
  2. return :( println("Hello, ", $name) )
  3. end
  4. @sayhello (macro with 1 method)

这个宏接受一个参数 name。当遇到 @sayhello 时,quoted 表达式会被展开并将参数中的值插入到最终的表达式中:

实例

  1. julia> @sayhello("human")
  2. Hello, human

我们可以使用函数 macroexpand 查看引用的返回表达式:

实例

  1. julia> ex = macroexpand(Main, :(@sayhello("human")) )
  2. :(Main.println("Hello, ", "human"))
  3. julia> typeof(ex)
  4. Expr

我们可以看到 “human” 字面量已被插入到表达式中了。

还有一个宏 @ macroexpand,它可能比 macroexpand 函数更方便:

实例
julia> @macroexpand @sayhello "human" :(println("Hello, ", "human"))f


该分类下的相关小册推荐:

暂无相关推荐.