Julia 编程 - 元编程

元编程可以定义为我们编写 Julia 代码来处理和修改 Julia 代码的编程。 借助 Julia 元编程工具,人们可以编写 Julia 编程代码来修改源代码文件的其他部分。 这些工具甚至可以控制修改后的代码何时运行。

以下是原始源代码的执行阶段 −

阶段 1 − 原始 Julia 代码已解析

在此阶段,原始 Julia 代码将转换为适合评估的形式。 该阶段的输出是AST,即抽象语法树。 AST 是一种以易于操作的格式包含所有代码的结构。

阶段 2 − 解析后的 Julia 代码被执行

在此阶段,执行已评估的 Julia 代码。 当我们在 REPL 中键入代码并按 Return 键时,这两个阶段就会发生,但它们发生得如此之快,以至于我们甚至没有注意到。 但是使用元编程工具,我们可以在两个阶段之间访问 Julia 代码,即在代码解析之后但在评估之前。

引用表达式

正如我们所讨论的,通过元编程,我们可以在两个阶段之间访问 Julia 代码。 为此,Julia 有":"冒号前缀运算符。 在冒号运算符的帮助下,Julia 存储未计算但已解析的表达式。

示例

   julia> ABC = 100
   100
      
   julia> :ABC
:ABC

这里 − ABC 是 Julia 的带引号或未计算的符号,即"ABC "是一个未计算的符号,而不是值为 100。

我们可以引用下面的整个表达式 −

julia> :(100-50)
:(100 - 50)

或者,我们也可以使用 quote…end 关键字来括起并引用表达式,如下所示 −

julia> quote
         100 - 50
      end
quote
   #= REPL[43]:2 =#
   100 - 50
end
Check this also:
julia> expression = quote
         for x = 1:5
            println(x)
         end
      end
quote
   #= REPL[46]:2 =#
   for x = 1:5
      #= REPL[46]:3 =#
      println(x)
   end
end

julia> typeof(expression)
Expr

它表明表达式对象已被解析、准备就绪并可供使用。

计算表达式

解析表达式后,还有一种方法可以计算表达式。 我们可以使用函数 eval() 来达到此目的,如下所示 −

julia> eval(:ABC)
100

julia> eval(:(100-50))

50

julia> eval(expression)
1
2
3
4
5

在示例中,我们评估了上面部分中解析的表达式。

抽象语法树 (AST)

如上所述,抽象语法树(AST)是一种以易于操作的格式包含所有代码的结构。 这是stage1的输出。 它使我们能够轻松地处理和修改 Julia 代码。 我们可以借助 dump() 函数可视化表达式的层次结构。

示例

julia> dump(:(1 * cos(pi/2)))
Expr
   head: Symbol call
   args: Array{Any}((3,))
      1: Symbol *
      2: Int64 1
      3: Expr
         head: Symbol call
         args: Array{Any}((2,))
            1: Symbol cos
            2: Expr
               head: Symbol call
               args: Array{Any}((3,))
                  1: Symbol /
                  2: Symbol pi
                  3: Int64 2

表达式插值

任何包含字符串或表达式的 Julia 代码通常都不会被求值,但借助美元 ($) 符号(字符串插值运算符),我们可以对某些代码求值。 当在字符串内部使用字符串插值运算符时,Julia 代码将被评估并将结果值插入到字符串中。

示例

julia> "the cosine of 1 is $(cos(1))"
"the cosine of 1 is 0.5403023058681398"

类似地,我们可以使用这个字符串插值运算符来包含插入到未计算表达式中的执行 Julia 代码的结果 −

julia> quote ABC = $(cos(1) + tan(1)); end
quote
   #= REPL[54]:1 =#
   ABC = 2.097710030523042
end

我们现在知道创建和处理未计算的表达式。 在本节中,我们将了解如何修改它们。 Julia 提供的宏接受未计算的表达式作为输入并生成新的输出表达式。

如果我们谈论它的工作原理,Julia 首先解析并计算宏,然后宏生成的处理后的代码将像普通表达式一样进行计算。

定义宏的语法与定义函数的语法非常相似。 以下是宏的定义,它将打印出我们传递给它的内容的内容 −

julia> macro x(n)
            if typeof(n) == Expr
               println(n.args)
            end
            return n
         end
@x (macro with 1 method)

我们可以通过在宏名称前加上 @ 前缀来运行宏 −

julia> @x 500
500

julia> @x "Tutorialspoint.com"
"Tutorialspoint.com"

eval() and @eval

Julia 有 eval() 函数和一个名为 @eval 的宏。 让我们看例子来了解它们的区别 −

julia> ABC = :(100 + 50)
:(100 + 50)

julia> eval(ABC)
150

上面的输出显示 eval() 函数扩展了表达式并对其求值。

julia> @eval ABC
:(100 + 50)

julia> @eval $(ABC)
150

也可以这样处理 −

julia> @eval $(ABC) == eval(ABC)
true

扩展宏

macroexpand() 函数返回指定宏的扩展格式(由 Julia 编译器在最终执行之前使用)。

示例

julia> macroexpand(Main, quote @p 1 + 4 - 6 * 7 / 8 % 9 end)
Any[:-, :(1 + 4), :(((6 * 7) / 8) % 9)]
quote
   #= REPL[69]:1 =#
   (1 + 4) - ((6 * 7) / 8) % 9
end