当前位置:K88软件开发文章中心编程语言非主流编程语言Julia → 文章内容

Julia 元编程

减小字体 增大字体 作者:佚名  来源:网上搜集  发布时间:2019-1-15 16:28:17

由 陈 创建, 最后一次修改 2016-08-12 元编程类似 Lisp ,Julia 自身的代码也是语言本身的数据结构。由于代码是由这门语言本身所构造和处理的对象所表示的,因此程序也可以转换并生成自身语言的代码。元编程的另一个功能是反射,它可以在程序运行时动态展现程序本身的特性。表达式和求值Julia 代码表示为由 Julia 的 Expr 类型的数据结构而构成的语法树。下面是 Expr 类型的定义:type Expr head::Symbol args::Array{Any,1} typendhead 是标明表达式种类的符号;args 是子表达式数组,它可能是求值时引用变量值的符号,也可能是嵌套的 Expr 对象,还可能是真实的对象值。 typ 域被类型推断用来做类型注释,通常可以被忽略。有两种“引用”代码的方法,它们可以简单地构造表达式对象,而不需要显式构造 Expr 对象。第一种是内联表达式,使用 : ,后面跟单表达式;第二种是代码块儿,放在 quote ... end 内部。下例是第一种方法,引用一个算术表达式:julia> ex = :(a+b*c+1):(a + b * c + 1)julia> typeof(ex)Exprjulia> ex.head:calljulia> typeof(ans)Symboljulia> ex.args4-element Array{Any,1}: :+ :a :(b * c) 1julia> typeof(ex.args[1])Symboljulia> typeof(ex.args[2])Symboljulia> typeof(ex.args[3])Exprjulia> typeof(ex.args[4])Int64下例是第二种方法:julia> quote x = 1 y = 2 x + y endquote # none, line 2: x = 1 # line 3: y = 2 # line 4: x + yend符号: 的参数为符号时,结果为 Symbol 对象,而不是 Expr :julia> :foo:foojulia> typeof(ans)Symbol在表达式的上下文中,符号用来指示对变量的读取。当表达式被求值时,符号的值受限于符号的作用域(详见变量的作用域)。有时, 为了防止解析时产生歧义,: 的参数需要添加额外的括号:julia> :(:):(:)julia> :(::):(::)Symbol 也可以使用 symbol 函数来创建,参数为一个字符或者字符串:julia> symbol('\''):'julia> symbol("'"):'求值和内插指定一个表达式,Julia 可以使用 eval 函数在 global 作用域对其求值。julia> :(1 + 2):(1 + 2)julia> eval(ans)3julia> ex = :(a + b):(a + b)julia> eval(ex)ERROR: a not definedjulia> a = 1; b = 2;julia> eval(ex)3每一个组件 有在它全局范围内评估计算表达式的 eval 表达式。传递给 eval 的表达式不限于返回一个值 - 他们也会具有改变封闭模块的环境状态的副作用:julia> ex = :(x = 1):(x = 1)julia> xERROR: x not definedjulia> eval(ex)1julia> x1表达式仅仅是一个 Expr 对象,它可以通过编程构造,然后对其求值:julia> a = 1;julia> ex = Expr(:call, :+,a,:b):(+(1,b))julia> a = 0; b = 2;julia> eval(ex)3注意上例中 a 与 b 使用时的区别:表达式构造时,直接使用变量 a 的值。因此,对表达式求值时 a 的值没有任何影响:表达式中的值为 1,与现在 a 的值无关表达式构造时,使用的是符号 :b 。因此,构造时变量 b 的值是无关的—— :b 仅仅是个符号,此时变量 b 还未定义。对表达式求值时,通过查询变量 b 的值来解析符号 :b 的值这样构造 Expr 对象太丑了。Julia 允许对表达式对象内插。因此上例可写为:julia> a = 1;julia> ex = :($a + b):(+(1,b))编译器自动将这个语法翻译成上面带 Expr 的语法。代码生成Julia 使用表达式内插和求值来生成重复的代码。下例定义了一组操作三个参数的运算符: ::for op = (:+, :*, :&, :|, :$) eval(quote ($op)(a,b,c) = ($op)(($op)(a,b),c) end)end上例可用 : 前缀引用格式写的更精简: ::for op = (:+, :*, :&, :|, :$) eval(:(($op)(a,b,c) = ($op)(($op)(a,b),c)))end使用 eval(quote(...)) 模式进行语言内的代码生成,这种方式太常见了。Julia 用宏来简写这个模式: ::for op = (:+, :*, :&, :|, :$) @eval ($op)(a,b,c) = ($op)(($op)(a,b),c)end@eval 宏重写了这个调用,使得代码更精简。 @eval 的参数也可以是块代码: @eval begin # multiple linesend对非引用表达式进行内插,会引发编译时错误:julia> $a + bERROR: unsupported or misplaced expression $宏宏有点儿像编译时的表达式生成函数。正如函数会通过一组参数得到一个返回值,宏可以进行表达式的变换,这些宏允许程序员在最后的程序语法树中对表达式进行任意的转化。调用宏的语法为:@name expr1 expr2 ...@name(expr1, expr2, ...)注意,宏名前有 @ 符号。第一种形式,参数表达式之间没有逗号;第二种形式,宏名后没有空格。这两种形式不要记混。例如,下面的写法的结果就与上例不同,它只向宏传递了一个参数,此参数为多元组 (expr1, expr2, ...) : @name (expr1, expr2, ...)程序运行前, @name 展开函数会对表达式参数处理,用结果替代这个表达式。使用关键字 macro 来定义展开函数:macro name(expr1, expr2, ...) ... return resulting_exprend下例是 Julia 中 @assert 宏的简单定义:macro assert(ex) return :($ex ? nothing : error("Assertion failed: ", $(string(ex))))end这个宏可如下使用:julia> @assert 1==1.0julia> @assert 1==0ERROR: Assertion failed: 1 == 0 in error at error.jl:22宏调用在解析时被展开为返回的结果。这等价于:1==1.0 ? nothing : error("Assertion failed: ", "1==1.0")1==0 ? nothing : error("Assertion failed: ", "1==0")上面的代码的意思是,当第一次调用表达式 :(1==1.0) 的时候,会被拼接为条件语句,而 string(:(1==1.0)) 会被替换成一个断言。因此所有这些表达式构成了程序的语法树。然后在运行期间,如果表达式为真,则返回 nothing,如果条件为假,一个提示语句将会表明这个表达式为假。注意,这里无法用函数来代替,因为在函数中只有值可以被传递,如果这么做的话我们无法在最后的错误结果中得到具体的表达式是什么样子的。在标准库中真实的 @assert 定义要复杂一些,它可以允许用户去操作错误信息,而不只是打印出来。和函数一样宏也可以有可变参数,我们可以看下面的这个定义:macro assert(ex, msgs...) msg_body = isempty(msgs) ? ex : msgs[1] msg = string("assertion failed: ", msg_body) return :($ex ? nothing :

[1] [2] [3]  下一页


Julia 元编程