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

Julia 元编程

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

error($msg))end现在根据参数的接收数目我们可以把 @assert 分为两种操作模式。如果只有一个参数,表达式会被 msgs 捕获为空,并且如上面所示作为一个更简单的定义。如果用户填上第二个参数, 这个参数会被作为打印参数而不是错误的表达式。你可以在下面名为 macroexpand 的函数中检查宏扩展的结果:julia> macroexpand(:(@assert a==b)):(if a == b nothing else Base.error("assertion failed: a == b") end)julia> macroexpand(:(@assert a==b "a should equal b!")):(if a == b nothing else Base.error("assertion failed: a should equal b!") end)在实际的 @assert 宏定义中会有另一种情况:如果不仅仅是要打印 "a should equal b,",我们还想要打印它们的值呢?有些人可能天真的想插入字符串变量如:@assert a==b "a ($a) should equal b ($b)!",但是这个宏不会如我们所愿的执行。你能看出是为什么吗?回顾字符串的那一章,一个字符串的重写函数,请进行比较:julia> typeof(:("a should equal b"))ASCIIString (constructor with 2 methods)julia> typeof(:("a ($a) should equal b ($b)!"))Exprjulia> dump(:("a ($a) should equal b ($b)!"))Expr head: Symbol string args: Array(Any,(5,)) 1: ASCIIString "a (" 2: Symbol a 3: ASCIIString ") should equal b (" 4: Symbol b 5: ASCIIString ")!" typ: Any所以现在不应该得到一个面上的字符串 msg_body,这个宏接收整个表达式且需要如我们所期望的计算。这可以直接拼接成返回的表达式来作为 string 调用的一个参数。通过看 error.jl源码得到完整的实现。@assert 宏极大地通过宏替换实现了表达式的简化功能。卫生宏卫生宏是个更复杂的宏。一般来说,宏必须确保变量的引入不会和现有的上下文变量发送冲突。相反的,宏中的表达式作为参数应该可以和上下文代码有机的结合在一起,进行交互。另一个令人关注的问题是,当宏用不同方式定义的时候是否被应该称为另一种模式。在这种情况下,我们需要确保所有的全局变量应该被纳入正确的模式中来。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 endendt0, t1, 及 val 应为私有临时变量,而 time 是标准库中的 time 函数,而不是用户可能使用的某个叫 time 的变量( println 函数也如此)。Julia 宏展开机制是这样解决命名冲突的。首先,宏结果的变量被分类为本地变量或全局变量。如果变量被赋值(且未被声明为全局变量)、被声明为本地变量、或被用作函数参数名,则它被认为是本地变量;否则,它被认为是全局变量。本地变量被重命名为一个独一无二的名字(使用 gensym 函数产生新符号),全局变量被解析到宏定义环境中。但还有个问题没解决。考虑下例: module MyModuleimport Base.@timetime() = ... # compute something@time time()end此例中, ex 是对 time 的调用,但它并不是宏使用的 time 函数。它实际指向的是 MyModule.time 。因此我们应对要解析到宏调用环境中的 ex 代码做修改。这是通过 esc 函数的对表达式“转义”完成的:macro time(ex) ... local val = $(esc(ex)) ...end这样,封装的表达式就不会被宏展开机制处理,能够正确的在宏调用环境中解析。必要时这个转义机制可以用来“破坏”卫生,从而引入或操作自定义变量。下例在调用环境中宏将 x 设置为 0 :macro zerox() return esc(:(x = 0))endfunction foo() x = 1 @zerox x # is zeroend应审慎使用这种操作。非标准字符串文本字符串中曾讨论过带标识符前缀的字符串文本被称为非标准字符串文本,它们有特殊的语义。例如:r"^\s*(?:#|$)" 生成正则表达式对象而不是字符串b"DATA\xff\u2200" 是字节数组文本 [68,65,84,65,255,226,136,128]事实上,这些行为不是 Julia 解释器或编码器内置的,它们调用的是特殊名字的宏。例如,正则表达式宏的定义如下: macro r_str(p) Regex(p)end因此,表达式 r"^\s*(?:#|$)" 等价于把下列对象直接放入语法树:Regex("^\\s*(?:#|\$)")这么写不仅字符串文本短,而且效率高:正则表达式需要被编译,而 Regex 仅在 代码编译时 才构造,因此仅编译一次,而不是每次执行都编译。下例中循环中有一个正则表达式:for line = lines m = match(r"^\s*(?:#|$)", line) if m == nothing # non-comment else # comment endend如果不想使用宏,要使上例只编译一次,需要如下改写: re = Regex("^\\s*(?:#|\$)")for line = lines m = match(re, line) if m == nothing # non-comment else # comment endend由于编译器优化的原因,上例依然不如使用宏高效。但有时,不使用宏可能更方便:要对正则表达式内插时必须使用这种麻烦点儿的方式;正则表达式模式本身是动态的,每次循环迭代都会改变,生成新的正则表达式。不止非标准字符串文本,命令文本语法( echo "Hello, $person")也是用宏实现的:macro cmd(str) :(cmd_gen($shell_parse(str)))end当然,大量复杂的工作被这个宏定义中的函数隐藏了,但是这些函数也是用 Julia 写的。你可以阅读源代码,看看它如何工作。它所做的事儿就是构造一个表达式对象,用于插入到你的程序的语法树中。反射除了使用元编程语法层面的反思,朱丽亚还提供了一些其他的运行时反射能力。类型字段 数据类型的域的名称(或模块成员)可以使用 names 命令来询问。例如,给定以下类型:type Point x::FloatingPoint yendnames(Point) 将会返回指针 Any[:x, :y]。在一个 Point 中每一个域的类型都会被存储在指针对象的 types域中:julia> typeof(Point)DataTypejulia> Point.types(FloatingPoint,Any)亚型 任何数据类型的直接亚型可以使用 subtypes(t::DataType) 来列表查看。例如,抽象数据类型 Fl

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


Julia 元编程