在编写Julia代码的过程中,会发现与其他语言有不少相似之处,例如Matlab、R或Python,但也有着明显的不同。通过与其他语言的对比,我们可以更加深入地理解Julia,也能够在熟悉其他语言的基础上,更快地学习Julia语言。
与Python相比
Python语言与Julia同为动态语言,都有着极为高效简洁的开发风格,主要差异有:
- Julia代码编写格式无要求
众所周知,Python在代码编写时,是需要“游标卡尺”的,格式就是语法规则的一部分,而Julia语言没有这方面的限制,以关键字对(以end结束)形成构造完整的结构。
- Julia中没有Python中的pass关键字
- Julia中对数组、字符串等的索引是从1开始的,而不是像Python等语言那样从0开始。
- 数组Slice索引时,Julia取得最后的元素
在对数组进行slice索引时,Julia按slice结构包括最后一个元素,但在Python最后一个元素并不在slice中。例如Julia中的a[2:3]取得第二个及第三个元素,相当于Python中的a[1:3],这是有所不同的,需要特别注意。
- Julia不支持负值索引
在对数组索引时,Julia不支持Python中以负值的方式按倒序取得数组中的元素。但Julia可以通过end关键字取得数组中的最后一个元素,并可通过end-n获得倒数n个元素。
- Julia数组采用列序存储,而Python中的NumPy默认采用的则是行序存储方式。
- 运算符%在Julia中是取余操作,在Python中则是取模操作。
- Julia更新运算符不会就地修改原变量
需要注意的是, Julia中的更新运算符与Python有着很大的不同,包括+=,-=等,不会就地修改原变量,例如:
julia> A = [1 1 1 1]
1×4 Array{Int64,2}:
1 1 1 1
julia> B = A;
julia> B
1×4 Array{Int64,2}:
1 1 1 1
julia> B += 3
1×4 Array{Int64,2}:
4 4 4 4
julia> B
1×4 Array{Int64,2}:
4 4 4 4
julia> A
1×4 Array{Int64,2}:
1 1 1 1
其中的A并没有被改变,只是B被改变了。运算符+=只是相当于B=B+3操作,在此过程中,B=A建立的引用关系被解除,变量名B被重新绑定到了B+3生成的新数组上。
- Julia参数默认表达式总会重新求值
在函数采用默认参数值的情况下,在函数每次运行时,Julia都会重新执行参数值中的表达式,不像Python那样只运行一次,这会带来很大的不同。例如在Julia中:
julia> f(x =rand()) = x
f (generic function with 2 methods)
julia> f()
0.8382948989948376
julia> f()
0.8835315579456324
julia> f()
0.32548534620112024
函数f()的每一次调用,参数rand()都会被执行,导致结果出现不同。而在Python中,则有着完全不同的表现:
>>> import random
>>> def f(x = random.randint(0,100)):
... print x
...
>>> f()
24
>>> f()
24
>>> f()
24
可见参数中虽然使用了random生成随机数,但每次f()被调用,返回的都是同一个值,这是因为Python在函数被第一次调用时,参数表中的表达式就会被计算并被固化下来。
所以,如果读者原来熟悉Python,转而学习Julia语言,对这些不同点需要非常的小心,否则很容易出现难以察觉的错误。
与Matlab相比
Julia与Matlab有不少相似之处,但在功能、句法等方面又有着很多的不同,包括:
- Julia中数组下标采用方括号,而Matlab是圆括号。
- Julia函数传参采用共享机制,复杂结构是引用方式;而Matlab是简单的传值。
- 数组赋值在Julia中只是将原数据绑定到了新的名字,可以认为是新的引用,所以不会像Matlab那样要新建数组并赋值数据。
- Julia不会在对数组元素的赋值时自动增长数组
在Matlab中,a(4) =3.2会自动创建数组a=[0 0 0 3.2];而a(5)=7时,因5超出a的长度导致a被自动增长,变成 [0 0 0 3.2 7]。但在Julia中,a[5]=7对元素的赋值时,如果5超出a实际长度,会抛出异常。若要对数组增加元素,Julia通过函数push!()或append!()进行,这种方法比Matlab中的a(end+1)=val这种方式更为高效。
- 复数虚部在Julia中的标识符为im,而Matlab采用i或j标识符。
- Matlab默认字面值为浮点数,但Julia会自动判断其最合适的类型。
- Julia有真实的向量结构,而不是通过矩阵模拟。
- 范围表达式在Julia中是独立类型
在Julia中,表达式a:b或a:b:c会构造类型为Range的独立对象,而不会像Matlab那样自动展开为向量。Julia可使用collect()将Range对象变成数组,但在实际使用中一般无需这种转换,因为在大部分情况下其使用与数组是一致的。而且因为懒计算的特点,有着更高的性能。这种对数组有一定代替性的Range对象,在一些函数或迭代操作中极为常用。
- 两者返回结果的方式不同
Julia函数会将最后一个表达式的结果返回,除非有显式的return调用。若结果是多个,Julia则会自动将之组装为Tuple类型;Matlab则采用了nargout变量,描述返回值。
- Julia在运行期间,没有严格的工作目录的概念。
- Julia中排序函数sort()默认按列进行
Julia对数组调用sort(A)等价于sort(A,1),如果要对1×N数组的所有元素进行排序,使用sort(A)不会获得预期效果,通过sort(A,2)才可以。
- 两者在数组比较运算时有所不同
在Julia中,如果A与B是数组,对它们的逻辑比较操作,如A==B则并不会返回布尔型的数组,给出每个元素的比较结果,而是要用点操作的方式,即A.==B才会得到这样的比较结果。
- 在Julia中,位运算符&(与),|(或)和⊻(异或)分别等价于Matlab中的and操作符、or操作符及xor操作符,但在操作优先级方面略有不同。
- Julia会自动检测跨行的表达式或语句结构,并不采用省略符来连接跨行的表达式。
- 内置变量ans在Matlab的脚本中也会起效,但Julia的脚本中会被忽略。
- Julia中声明的类型无法在运行期间像Matlab的class那样能够动态增加成员。如果涉及,Julia中最好采用Dict类型代替。
- Maltab中只有一个全局作用域,但在Julia中每个模块都有着自己独立的全局作用域及命名空间。
- 两者在过滤数组元素的方式上有所不同
在Matlab中,若要移除数组中不需要的元素,需通过逻辑索引就地修改原数组,例如x(x>3)或x(x>3)=[]选择或去除值>3的元素。在Julia中,提供了专门的高阶函数filter()及filter!()可用于达到此目的,例如可使用filter(z->z>3, x)或filter!(z->z>3,x)实现上述的功能。而且此两个函数能够减少临时内存的使用,有着较高的性能。
与R相比
相比R语言,Julia在性能方面有着明显的优势。两者在语法及使用上差异主要是:
- 单引号在Julia中用于表达某个字符值,而非是字符串类型。
- R中要获得子字符串,需先将其转换为字符向量,但Julia中可直接以索引的方式取得。
- 取模操作在R中是a %% b,在Julia中则是mod(a,b),%用于求取余数。
- 创建向量时,Julia的[1,2,3]等效于R中的c(1,2,3)。
- Julia中逻辑索引需原数组维度与阶数一致
在R语言中,c(1, 2, 3, 4)[c(TRUE, FALSE, TRUE, FALSE)]或c(1, 2, 3, 4)[c(TRUE, FALSE)]均可获得c(1, 3)这样的结果。但在Julia中,[1, 2, 3, 4][[true, false]]会抛出BoundsError异常,而[1, 2, 3, 4][[true, false, true, false]]才会获得想要的结果。
- Julia二元运算符操作数均为数组时,维度与阶数需一致
在对两个数组操作时,Julia一般要求它们的维度与阶数一致,例如[1, 2, 3, 4] + [1, 2]这样的表述会抛出错误。而R语言只要求两者重叠即可,即c(1, 2, 3, 4) + c(1, 2)是有效的。
- Julia的map()函数与R中的lapply()类似,但参数表述有差异。
- Julia的矢量化操作更为简单
R中的mapply(choose,11:13,1:3)类似于Julia的broadcast(binomial,11:13,1:3)。而Julia提供了更为简洁的点运算,即binomial.(11:13,1:3)便可达到相同的效果。
- 运算符->在Julia用于定义匿名函数,但在R中用于赋值操作。
- Julia中没有R中的<-与<<-操作符。
- 在对向量与矩阵进行连接时,Julia使用的是hcat()、vcat()及hvcat()函数,对应于R中的c()、rbind()及cbind()函数。
- Julia中矩阵乘法使用A*B,R中需使用A %*% B。
- 矩阵逐元乘积Julia中使用点运算A.*B实现,R中的则是A*B。
Julia的A’等效于R中的t(A),用于求解矩阵的转置,而!操作符则用于共轭转置。
- 在Julia中,使用if结构、for及while时,条件表达式无需圆括号
- Julia中数值1或0不能当布尔型用
- 获得数组阶数,Julia使用size()函数,R使用nrow()及ncol()函数。
- Julia的类型限定更为严格
在Julia中,标量、向量及矩阵都是不同的,但R语言中标量1与向量c(1)是相同的。
- Julia 中的diag()及diagm()与R的同名函数有着不同的功能。
- 在Julia中,函数结果不能直接作为赋值的左操作数,例如diag(A)=ones(n)是错误的。
- Julia有着完善的自定义类型机制,也更为灵活便捷,不像R要借助S3或S4对象。
- Julia有着更高效的参数传递机制
函数调用时,Julia采用共享传参机制,复杂类型通过引用方式传递。与R中的传值方式相比,显然有着更为高效的性能。
- Julia中范围表达式有独立的类型
表达式a:b在R中是向量的简洁表述,但仍是向量;但在Julia中是独立的Range类型,是一种迭代器,不会自动展开为数组,在节省内存方面有着很大的优势。
- 两者的最大最小等函数存在差异
Julia的max()和min()函数类似于R中的pmax()及pmin()函数;而R中的max()及min()对应于Julia中的maximum()及minimum()函数。
- 两者中矢量化计算的意义有差异
R语言的代码如果要提升性能,往往要进行矢量化改造,但Julia中的矢量化计算多是一种方便的形式,不是性能的关键。
------- 节选自本人新书内容:魏坤著《Julia语言程序设计》,机械工业华章图文出版。