原文链接:点击打开链接
摘要: 当前版本 v0.5 因为在知乎上写的量子计算札记会涉及到使用Julia语言的数值模拟,同时随着中国的Julian越来越多,而之前几个在JuliaCN活跃的老司机最近一直没时间翻译文档,然后最近好像也没什么人给贡献翻译OTZ。
当前版本 v0.5
因为在知乎上写的量子计算札记会涉及到使用Julia语言的数值模拟,同时随着中国的Julian越来越多,而之前几个在JuliaCN活跃的老司机最近一直没时间翻译文档,然后最近好像也没什么人给贡献翻译OTZ。所以呢,先写一个简单的教程给大家用。有很多地方参考了英文的标准文档。感兴趣的同学可以直接戳英文文档,也欢迎大家给中文文档贡献翻译。极客学院上也有一些教程,你也可以参考那个。知乎好像没Julia高亮or我哪里没有设置对,如果有强迫症可以戳我blog上的:一个简单的Julia教程|Half Integer
声明这个教程应该适用于已经学过一门编程语言,想快速掌握Julia语法的人。所以
我们可以新建一个脚本文件hello.jl,在里面写
print("hello world\n")
我们先不要管这行命令具体是怎么回事,你知道它是用来打印Hello World的就可以啦。然后在命令行里用Julia的解释器执行它
$ julia hello.jl
hello world
或者让我们打开Julia的REPL
然后你就进入到了一个交互式的窗口中,输入这个打印Hello World的命令
julia> print("hello world!\n")
hello world!
那么这个print函数是干嘛的呢?我们可以在REPL中输入?+print+enter来获得它的文档
julia>
help?>print
search: print println print_joined print_escaped print_shortest print_unescaped
print(x)
Write (to the default output stream) a canonical (un-decorated) text
representation of a value if there is one, otherwise call show. The
representation used by print includes minimal formatting and tries to avoid
Julia-specific details.
那么我们就知道了print是说打印输入的变量的意思
安装好Julia之后,按Ctrl+Shift+T打开命令行(Windows用户请按Ctrl+X,点击运行输入cmd,打开命令行),可以直接运行Julia命令。
$ julia -e 'print("hello world!\n")'
hello world!
这是Julia的第一种执行命令的方式,-e 选项后面用'括起你要执行的Julia代码,但注意因为回车会让命令行执行这行命令所以你必须把所有的代码写成一行。一般我们只用这种方式执行一些很短的,很简单的代码。
让我们打开一个REPL,在里面测试这一小节的内容。首先,Julia的变量不需要特别的声明,变量类型会由你所给它的值自动确定
# 将10赋值给变量x
julia> x=10
10
# 对变量x进行运算
julia> x+1
11
# 重新给x赋值
julia> x= 1+1
2
# 但是请不要用数字作为变量名的开头,因为Julia中默认数字开头代表了乘法
julia> 2x
4
# 也可以给x赋其它类型的值
julia> x = "hello world!"
hello world
Julia的变量名称支持大部分的Unicode(有极少数的不能支持,但我们不需要管),所以你可以这样
julia> δ = 0.00001
1e-5
julia> 你好 = "hello"
hello
这些数学符号可以使用LaTeX语法输入,中文的话用你自己的输入法就可以啦,Julia支持了包括UTF-8在内的多种编码。在REPL中可以这样输入一个希腊字母\delta+Tab = δ, 而在编程的时候我们当然不能用命令行,所以有这样几个编辑器支持这种补全(如果你发现还有别的好用的编辑器不妨在评论里告诉我):
Julia有一些自带的常量,这些常量是允许重载的,但是一般不建议重载它们。比如
julia> pi
π = 3.1415926535897...
julia> pi = 3
WARNING: imported binding for pi overwritten in module Main
3
julia> π
3
整数从8位整数支持到128位整数,而浮点数从16位支持到64位浮点数,如果有高精度计算需求还可以使用内建的高精度浮点数BigFloat(256位),整数Int默认跟随系统位数,比如在32位系统上为32位整数类型Int32,但64位系统就是Int64
在Julia中,虚数单位用im来表示,Julia为虚数对象定义了一些基本的函数
julia> 1+2im
1 + 2im
# 实部
julia> real(1+2im)
1
# 虚部
julia> imag(1+2im)
2
# 共轭
julia> conj(1+2im)
1 - 2im
# 取模
julia> abs(1+2im)
2.23606797749979
julia> norm(1+2im)
2.23606797749979
# 求幅角
julia> angle(1+2im)
1.1071487177940904
Julia中定义了一些常用的运算符,它们有
Julia的语句可以默认以一行结尾,比如
x+1
y+2
这里x+1和y+1就是分割开的两条语句,但是我们也可以用;分割语句,比如
x+1;y+1
z+2
Julia的代码块都以end关键字结尾,类似于Pascal,相当于C/C++中的{}声明代码块的方式。也相当于Python中使用空格对齐来分割代码块。
Julia的类型系统是很有特点的,也常常被认为是Julia的速度快的原因之一。Julia的类型系统不支持类似于Python/Cpp那样以对象的成员函数和继承构成的OOP编程范式。Julia中面向对象的编程使用了Multiple Dispatch这一特性,而禁止了类型(对象)中使用成员函数。这种特性继承自Lisp,称为OO,而多重派发是实现OO的一个重要手段。
具体关于OO技术的好处可以参看这篇博客:浅谈OO编程中必不可少的手段:Multimethod,关于类似于Cpp这种面向对象编程的弊端可以参见这个知乎问题:面向对象编程的弊端是什么
Julia中总共提供了四种类型,它们的关键字分别是:type,immutable,abstract,bittype,这里我们暂时只介绍前三种(最后)
在Julia中使用type关键字声明复合类型,以end关键字结尾。在type和end之间声明此类型所包含的变量
type Animal
weight
sex
end
当然也可以用分号来分割语句
type Animal
weight;sex
end
当然你也可以什么都不写,但这种时候建议你最好考虑一下后面介绍到的抽象类型。
type Animal
end
用type声明的类型在没有说明的时候默认以指针赋值,或者说默认使用引用的方式来赋值。除非使用deepcopy函数显式说明是深度拷贝。
复合类型中的元素可以通过.运算符来访问
julia> alpaca = Animal("肥的","母的")
Animal("肥的","母的")
julia> alpaca.sex
"母的"
Julia是一个强类型语言,我们可以用::符号来声明(或者标注)变量类型,比如
julia> (1+2)::AbstractFloat
ERROR: TypeError: typeassert: expected AbstractFloat, got Int64
julia> (1+2)::Int64
3
在复合类型中
type Animal
weight::Float64
sex::Int
end
指定类型往往能够让Julia程序的运行效率更高。
抽象类型使用abstract关键字声明,声明抽象类型一般有什么用呢?声明抽象类型可以很好的帮助你控制接口,具体的例子我们在后面的多重派发介绍。
abstract AbstractAnimal
抽象类型可以作为其它类型的父类型出现,比如
abstract Monkey <: AbstractAnimal
这里<:运算符代表:左边是右边的子类型,它可以在声明的时候使用,也可以用来做类型判断
julia> Monkey <: AbstractAnimal
true
复合类型也可以作为抽象类型的子类型,但不能作为复合类型的子类型。
type Donkey <: AbstractAnimal
alive::Bool
end
immutable Complex
real::Float64
imag::Float64
end
不可变类型是一种类似于type的类型,但是相对type有一些不同:
注意,不可变类型中的元素如果是可变类型的,比如数组,那么这个元素依然是可变的
immutable Animals
list_of_animals::Array
end
这里list_of_animals是可以改变的,但是Animals不能够被改变
julia> animals = Animals([1,2,3])
Animals([1,2,3])
julia> another_list = [2,3,4]
3-element Array{Int64,1}:
2
3
4
# Animals的元素不能改变
julia> animals.list_of_animals = another_list
ERROR: type Animals is immutable
# 但是Animals元素的元素可以改变
julia> animals.list_of_animals[2] = 100
100
julia> animals.list_of_animals
3-element Array{Int64,1}:
1
100
3
复合类型type和不可变类型immutable都可以用如下的方法产生实例
type foo1
a
b
end
foo1(1,2)
immutable foo2
a
b
end
foo2(2,3)
参数输入的顺序是每个类型的元素在类型中的顺序
Julia的类型也有类似于C++的模板(template)的功能,比如假如现在有Monkey,Donkey,Pig这样三个类型,都是AbstractAnimal的子类型。但是我想有一个Pet(宠物)类型,我可以养猴子,也能养驴,还能养猪当宠物,所以不能给Pet的成员指定某个类型,但我又不希望它被用来装别的类型。所以这个时候就需要参数类型。
type Monkey<:AbstractAnimal
end
type Donkey<:AbstractAnimal
end
type Pig<:AbstractAnimal
end
type Pet{T<:AbstractAnimal}
p::T
end
这样在生成这个类型的实例的时候,编译器就会生成相对应的类型,比如
julia> Pet{Monkey}(Monkey())
Pet{Monkey}(Monkey())
注意参数类型在产生实例的时候需要指定参数,编译器不会自己判断,当然我们有办法让编译器自己判断,这个在后面介绍。
除了类型意外,参数类型的参数还可以是别的东西,比如一个数字
type foo{N}
end
julia> foo{2}()
foo{2}()
在Julia中函数使用function关键字来声明
function add(a,b)
return a+b
end
返回值由return关键字指定,或者由最后一个变量指定
function foo()
"foo"
end
julia> foo()
"foo"
实际上对于比较短的函数,你还可以这样声明
f(x) = x^2
julia> f(2)
4
构造函数是用于定义如何构造对应类型的方法的函数。类似于其它面向对象语言,Julia的复合类型也有构造函数,并且是唯一可以在类型内声明的函数。构造函数默认是下面这样的,也就是需要按照内部元素的顺序来构造一个新的foo对象。
type foo
a::Int
b::Float64
end
f = foo(1,1.0)
实际上它相当于
type foo
a::Int
b::Float64
foo(a::Int, b::Float64) = new(a, b)
end
Julia的构造函数分为两种,一种是类型内的构造函数,一种是类型外的构造函数。类型外的构造函数实际上都是调用默认的构造函数创建对象,与一般的函数类似。而类型内的构造函数略有不同,类型内的构造函数可以访问new函数来为新对象的元素绑定值。new是一个用来给类型元素绑定值的函数,注意这个函数仅有在类型内的构造函数声明才能够访问。
lambda表达式可能会是我们很常用的一种函数,它也往往称为匿名函数,匿名函数的声明有两种方式
# 第一种
x->x^2+1
# 第二种
function (x)
return x->x^2+1
end
第一种声明方式后面需要跟一整个代码块,julia中可以使用begin,end来声明一段代码块(不过这个应该要算在metaprogramming的部分了?)
所以你可以这样
x->begin
if x == "蛤?"
return 1
else
return "excited"
end
end
而很多时候,我们都需要将匿名函数作为某个函数的第一个变量输入,比如像map,sum这种的。这个时候再写
map(x->begin
if x == "蛤?"
return 1
else
return "excited"
end
end, [1, "蛤?", 2])
未免有些不美观,影响可读性,尤其是里面这个匿名函数比较复杂的时候。不用担心,julia为这种比较长的匿名函数提供了一个可读性更好的声明方式
map([1, "蛤?", 2]) do x
if x == "蛤?"
return 1
else
return "excited"
end
end
do关键字会产生将后面的代码块(到end结束)创建成一个匿名函数然后传递给do前面的函数的第一个变量,比如map。有了这个特性,类似于Python的with关键字,我们在Julia中操作文件的时候也可以让它自动关闭
open("outfile", "w") do io
write(io, data)
end
和其它的编程语言类似,尤其是和python类似,julia的for和while也有很多语法糖。
比如
for i = 1:10
@show i
end
for i in [1,2,3,4,5,6,7,8,9]
@show i
end
等等。然后if,else语句类似于Python,但Python的elif变成了elseif
if x>0
println(1)
elseif x == 0
println(0)
else
println(3)
end
同时也支持expr?a:b形式的简写,意思是expr为true则执行a,false执行b
Julia的名字空间是通过模块module来进行管理的。
module foo
export foo1, foo2
############
# content
############
end
在module和end关键字中的代码块就会记在这个module下,使用的时候必须先将相应的module所开放的(export)的名字引入,方法有两种,一种是using,一种是import
using会将后面的模块中所有开放的变量暴露在当前的空间中,而import会引入相应的模块名称。举个例子,我使用QuDynamics这个模块的时候,如果
using QuDynamics,那么其中所开放的函数我都可以直接访问,比如propagate这个函数,我就可以直接使用它。而相应的import QuDynamics,就需要用.运算符访问模块的元素,也就是这个模块所开放的接口,例如QuDynamics.propagate
而Julia的package都是会由一个module进行封装,在安装的时候需要知道相应包的名字,比如Plots,然后使用Pkg.add命令进行安装。例如Pkg.add("Plots")。之后Julia的编译器就会访问在METADATA.jl中注册了的相应包的地址,然后从github上clone相关依赖。当然由于某些原因,这有时候会造成一些用户出现无法安装,或者下载缓慢的问题。这在第三版本的包管理器里也许会得到解决。