Julia 元编程

类似 Lisp ,Julia 自身的代码也是语言本身的数据结构。由于代码是由这门语言本身所构造和处理的对象所表示的,因此程序也可以转换并生成自身语言的代码。元编程的另一个功能是反射,它可以在程序运行时动态展现程序本身的特性。

表达式和求值

Julia 代码表示为由 Julia 的 Expr 类型的数据结构而构成的语法树。下面是 Expr 类型的定义:

type Expr
  head::Symbol
  args::Array{Any,1}
  typ
end

head 是标明表达式种类的符号;args 是子表达式数组,它可能是求值时引用变量值的符号,也可能是嵌套的 Expr 对象,还可能是真实的对象值。 typ 域被类型推断用来做类型注释,通常可以被忽略。

有两种“引用”代码的方法,它们可以简单地构造表达式对象,而不需要显式构造 Expr 对象。第一种是内联表达式,使用 : ,后面跟单表达式;第二种是代码块儿,放在 quote ... end 内部。下例是第一种方法,引用一个算术表达式:

julia> ex = :(a+b*c+1)
:(a + b * c + 1)

julia> typeof(ex)
Expr

julia> ex.head
:call

julia> typeof(ans)
Symbol

julia> ex.args
4-element Array{Any,1}:
  :+
  :a
  :(b * c)
 1

julia> typeof(ex.args[1])
Symbol

julia> typeof(ex.args[2])
Symbol

julia> typeof(ex.args[3])
Expr

julia> typeof(ex.args[4])
Int64
符号

: 的参数为符号时,结果为 Symbol 对象,而不是 Expr :

julia> :foo
:foo

julia> typeof(ans)
Symbol

求值和内插

指定一个表达式,Julia 可以使用 eval 函数在 global 作用域对其求值。

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

julia> eval(ans)
3

julia> ex = :(a + b)
:(a + b)

julia> eval(ex)
ERROR: a not defined

julia> a = 1; b = 2;

julia> eval(ex)
3
每一个组件 有在它全局范围内评估计算表达式的 eval 表达式。传递给 eval 的表达式不限于返回一个值 - 他们也会具有改变封闭模块的环境状态的副作用:
julia> ex = :(x = 1)
:(x = 1)

julia> x
ERROR: x not defined

julia> eval(ex)
1

julia> x
1

代码生成

Julia 使用表达式内插和求值来生成重复的代码。下例定义了一组操作三个参数的运算符: ::

for op = (:+, :*, :&, :|, :$)
  eval(quote
    ($op)(a,b,c) = ($op)(($op)(a,b),c)
  end)
end

宏有点儿像编译时的表达式生成函数。正如函数会通过一组参数得到一个返回值,宏可以进行表达式的变换,这些宏允许程序员在最后的程序语法树中对表达式进行任意的转化。调用宏的语法为:

@name expr1 expr2 ...
@name(expr1, expr2, ...)
注意,宏名前有 @ 符号。第一种形式,参数表达式之间没有逗号;第二种形式,宏名后没有空格。

卫生宏

卫生宏是个更复杂的宏。一般来说,宏必须确保变量的引入不会和现有的上下文变量发送冲突。相反的,宏中的表达式作为参数应该可以和上下文代码有机的结合在一起,进行交互。另一个令人关注的问题是,当宏用不同方式定义的时候是否被应该称为另一种模式。在这种情况下,我们需要确保所有的全局变量应该被纳入正确的模式中来。Julia 已经在宏方面有了很大的优势相比其它语言(比如 C)。所有的变量(比如 @assert中的 msg)遵循这一标准。

来看一下 @time 宏,它的参数是一个表达式。它先记录下时间,运行表达式,再记录下时间,打印出这两次之间的时间差,它的最终值是表达式的值:

macro time(ex)
  return quote
    local t0 = time()
    local val = $ex
    local t1 = time()
    println("elapsed time: ", t1-t0, " seconds")
    val
  end
end

t0t1, 及 val 应为私有临时变量,而 time 是标准库中的 time 函数,而不是用户可能使用的某个叫 time 的变量( println 函数也如此)。

Julia 宏展开机制是这样解决命名冲突的。首先,宏结果的变量被分类为本地变量或全局变量。如果变量被赋值(且未被声明为全局变量)、被声明为本地变量、或被用作函数参数名,则它被认为是本地变量;否则,它被认为是全局变量。本地变量被重命名为一个独一无二的名字(使用 gensym 函数产生新符号),全局变量被解析到宏定义环境中。

联系我们

邮箱 626512443@qq.com
电话 18611320371(微信)
QQ群 235681453

Copyright © 2015-2022

备案号:京ICP备15003423号-3