模块(module)是一些互相隔离的可变工作空间,也就是说它们会引入新的全局作用域。它们在语法上以 module Name … end 界定。模块允许你创建顶层定义(也称为全局变量),而无需担心命名冲突。在模块中,利用导入(importing)可以控制其它模块中的哪些名称是可见的;利用导出(exporting)可以控制你自己的模块中的哪些名称是公开的。
julia> module MyModule
export my_add, my_multiply
my_add(x, y) = x + y
my_multiply(x, y) = x * y
my_minus(x, y) = x - y
my_pow(x, y) = x ^ y
my_abs(x) = x >= 0 ? x : -x
end
Main.MyModule
# using后,export的方法可以直接使用
julia> my_add(1, 2)
3
julia> my_multiply(3, 4)
12
# 没有export的方法需要import
julia> my_minus(4, 3)
ERROR: UndefVarError: my_minus not defined
Stacktrace:
...
julia> import Main.MyModule.my_minus
julia> my_minus(4, 3)
1
# 也可以用全名访问模块内方法
julia> Main.MyModule.my_abs(-5)
5
模块与文件和文件名无关;模块只与模块表达式有关。一个模块可以有多个文件,一个文件也可以有多个模块。
有三个重要的标准模块:
每个 Julia 程序均以字符串开始:
julia> prog = "1 + 1"
"1 + 1"
julia> ex1 = Meta.parse(prog)
:(1 + 1)
# 也可以显式地用:(<Julia语句>)的方式构造表达式
# 表达式类型
julia> typeof(ex1)
Expr
# 计算
julia> eval(ex1)
2
Expr 对象包含两个部分:
julia> ex1.head
:call
julia> ex1.args
3-element Array{Any,1}:
:+
1
1
表达式也可以直接用 prefix notation 构造:
julia> ex2 = Expr(:call, :+, 1, 1)
:(1 + 1)
这里的关键点是 Julia 的代码在内部表示为可以从语言本身访问的数据结构
函数 dump 可以带有缩进和注释地显示 Expr 对象:
julia> dump(ex2)
Expr
head: Symbol call
args: Array{Any}((3,))
1: Symbol +
2: Int64 1
3: Int64 1
Expr 对象也可以嵌套:
julia> ex3 = Meta.parse("(4 + 4) / 2")
:((4 + 4) / 2)
julia> dump(ex3)
Expr
head: Symbol call
args: Array{Any}((3,))
1: Symbol /
2: Expr
head: Symbol call
args: Array{Any}((3,))
1: Symbol +
2: Int64 4
3: Int64 4
3: Int64 2
多行表达式可以在quote … end中实现
ex4 = quote
x = 1
y = 2
x + y
end
Julia 允许将字面量或表达式插入到被引用的表达式中。表达式插值由前缀 $ 表示。
julia> a = 1;
julia> ex = :($a + b)
:(1 + b)
Julia 能在其内部生成和操作 Julia 代码
julia> function math_expr(op, op1, op2)
expr = Expr(:call, op, op1, op2)
return expr
end
math_expr (generic function with 1 method)
julia> ex = math_expr(:+, 1, Expr(:call, :*, 4, 5))
:(1 + 4 * 5)
julia> eval(ex)
21
作为另一个例子,这个函数将数值参数加倍,但不处理表达式:
julia> function make_expr2(op, opr1, opr2)
opr1f, opr2f = map(x -> isa(x, Number) ? 2*x : x, (opr1, opr2))
retexpr = Expr(:call, op, opr1f, opr2f)
return retexpr
end
make_expr2 (generic function with 1 method)
julia> make_expr2(:+, 1, 2)
:(2 + 4)
julia> ex = make_expr2(:+, 1, Expr(:call, :*, 5, 8))
:(2 + 5 * 8)
宏提供了在程序的最终主体中包含所生成的代码的方法。宏将参数元组映射到所返回的表达式,且生成的表达式会被直接编译,并不需要运行时的 eval 调用。宏的参数可以包括表达式、字面量值和符号。
这是一个非常简单的宏:
julia> macro sayhello()
return :( println("Hello, world!") )
end
@sayhello (macro with 1 method)
宏在Julia的语法中有一个专门的字符 @ 。紧接着是其使用macro NAME … end 形式来声明的唯一的宏名。在这个例子中,编译器会把所有的@sayhello 替换成:
:( println("Hello, world!") )
当 @sayhello 在REPL中被输入时,解释器立即执行,因此我们只会看到计算后的结果。
现在,考虑一个稍微复杂一点的宏:
julia> macro sayhello(name)
return :( println("Hello, ", $name) )
end
@sayhello (macro with 1 method)
这个宏接受一个参数name。当遇到@sayhello时,quoted 表达式会被展开并将参数中的值插入到最终的表达式中:
julia> @sayhello("human")
Hello, human
@name expr1 expr2 ...
@name(expr1, expr2, ...)
在宏名称前的标志 @,且在第一种形式中参数表达式间没有逗号,而在第二种形式中 @name 后没有空格。这两种风格不应混淆。
宏把它们的参数作为表达式、字面量或符号接收。浏览宏参数的一种方法是在宏的内部调用 show 函数:
julia> macro showarg(x)
show(x)
# ... remainder of macro, returning an expression
end
@showarg (macro with 1 method)
julia> @showarg(a)
:a
julia> @showarg(1+1)
:(1 + 1)
julia> @showarg(println("Yo!"))
:(println("Yo!"))
Julia @assert宏:
julia> macro assert(ex)
return :( $ex ? nothing : throw(AssertionError($(string(ex)))) )
end
@assert (macro with 1 method)
使用:
julia> @assert 1 == 1.0
julia> @assert 1 == 0
ERROR: AssertionError: 1 == 0
@time 计算程序运行时间
@which 语句调用位置
@show 显示执行内容和结果