Coding Poet, Coding Science

Julia编程语言基础知识

也许这个编程语言才真正贴近科学计算的真实需求。见http://en.wikipedia.org/wiki/Julia_%28programming_language%29。在Ubuntu下面可以直接安装它。

基本的定位是,发明于2012年,多范式(面向对象、过程、函数式加元编程语言),具有动态类型,以及类型注解功能。受到很多高级动态语言的启发。同样地,它也是一个高级动态编程语言,但是对于高性能数值计算与科学计算非常重视。对于通用编程也有一定的效率。注:其实是2009年开发,2012年释出开源版本。

Julia具有一个参数类型的类型系统。使用并行与分布式计算,并且可以直接调用C与Fortran的接口库。而且具有自动内存回收的功能。它的浮点数库、线性代数库、随机数生成、快速傅里叶变换以及正则表达式匹配都非常有效率。目前的版本是0.4。

自己试用了Ubuntu上面的Julia,即使是在命令行界面之下,也有非常好的视觉效果。Julia有一个同名的交互式Shell(正式称呼是Julia REPL),便于学习和测试程序代码。

Julia直接管理在线的包。因为它的包可以直接从Github上面获取。Julia有JavaCall,Mathematica等,面向R的是Rcall。,可以直接从这些语言里面调用程序。

Julia的其中的一个目标是像R一样好用。Julia的加号等运算符同样地是一种语法糖。

Julia的核心是用C和C++写成的,解析器使用Schema,而Just-in-time编译器使用了LLVM的机制。Julia的JIT是通过LLVM实现的。

其实我们看重的还是Julia的语法功能,其表现力更重要。至于实现,则不用那么重要。就像对于Scala,我们看重的仍是其语法、语义模型等。

关于REPL: A read–eval–print loop (REPL), also known as an interactive toplevel or language shell, is a simple, interactive computer programming environment that takes single user inputs (i.e. single expressions), evaluates them, and returns the result to the user; a program written in a REPL environment is executed piecewise. The term is most usually used to refer to programming interfaces similar to the classic Lisp machine interactive environment. Common examples include command line shells and similar environments for programming languages.

实际上,可以使很多的语言都变成REPL方式工作。参考http://repl.it/languages上面的一个在线的REPL程序,包含了各种程序实现。

虽然julia命令直接打开一个REPL,但是官方还是推荐使用IJulia(以后会改名为Jupyter)。安装它是非常简单的,打开julia,输入Pkg.add(“IJulia”)即可。然后使用Pkg.update()或者Pkg.build(“IJulia”)可以执行更多的包管理的功能。启动IJulia的时候,可以通过打开julia控制台,然后输入:

using IJulia
notebook()

或者使用

ipython notebook --profile julia

下面还是浏览一下Julia的基本的功能

通过说明书http://docs.julialang.org/en/latest/manual/来看,内容还是比较简单的。但是里面其实不简单。包括了元编程,并行编程等概念。

下面我们一点点解决相关的问题。

在导言的部分,作者指出科学计算有追求高性能的传统。但是目前的情况下,这些科学计算的专家们经常使用较慢的动态语言当成日常的工作。现在随着编译技术的进步等,这些人现在可以使用动态语言的同时写出高效率的工作。同时,JIT、REPL等的进步,也使得交互式工作变得越来越容易了。Julia就在这种情况下应运而生。

Julia的设计其实是沿着经典的数学编程语言(mathematical programming languages)的设计路线,并不同于通用编程语言的路线(虽然从编程语言原理上看没有什么区别,但是在现实中,我们完全可以把通用编程语言,数学编程语言,以及领域专用语言看成是编程语言原理的不同的领域的应用,以致于在讲通用编程语言的机制的时候,几乎完全不适用于领域专用语言。现在,这些语言又有进一步融合的趋势。

作者提到,动态语言并不是没有类型的:虽然大多数情况下类型不用声明,而且会自动进行转换,结果开发人员好像不用考虑类型的问题一样。其实对于静态语言,我们也可以说,类型完全是编译期的一个概念,运行期完全没有那样的一回事。

变量与赋值

Julia当中的变量支持使用unicode的字符,但是变量名不能是保留字。赋值使用一般的等号,字符串字面量当中,Unicode是自然而然的。另外,也可以使用latex风格的变量名称,这其实就是把变量以数学模式开始和结束而已,没有什么好奇怪的。

Julia的建议的风格是:变量名以小写的字母开始,同时不使用下划线,而使用大写字母使变量显得紧凑。另外,如果是函数,建议最后加一个叹号,表示这个函数是一个改变状态的函数(也称为过程)。这样可以与函数式风格明显地区别开。

Julia对于常见的类型(整型、无符号整型、长整型、布尔型、字符型、浮点型)都有支持。Julia是动态类型语言,由此它有typeof函数,该函数接受一个变量为参数,然后返回一个类型。我们还可以把类型当成一个值赋给一个变量,这个变量就是一个类型变量了。下面是在Julia当中合法的操作:

my_type = typeof(123)
my_type <: Int

其中,<:是类的操作符,表示一个子类型。第二条语句运行的结果就是True,因为Int64确实是Int的子类型。另外,整数可以用0x之类的前缀表示,以支持不同进制的字面量输入。另外,使用isa(var, Type)可以判断某个变量是否属于某个类型。

Julia的类型系统直接暴露于程序员可见的范围。比如,通过typemin(T)、typemax(T)可以得到相关类型的表示范围中的极小值与极大值。这比C实践中的方法方便多了。注意,在Julia当中,实行的是所谓的模态算术。因此typemax(Int64)+1 == typemin(Int64)为真。也就是说,整数的加减是循环的。这种数学的处理,大大方便了对于数值操作的溢出的概念。使用bits函数,可以得到数据的内部字节表示。另外,Julia的浮点数的处理的机制按照IEEE 754规范,因此具有Inf、NaN之类的量。使用eps()函数,可以得到浮点类型的机器的最小精度。

浮点数与整数的处理,可以参考http://docs.julialang.org/en/release-0.3/manual/integers-and-floating-point-numbers/。个人觉得,这可以作为高级编程语言的进一步的知识,让学习者可以掌握编程语言处理科学数据的规范。

Julia实现了GMP与MPFR库基础上的变长精度浮点运算。任意长的整数的运算,使用的类型是BigInt。通过BigInt(“2342342342342”)可以制造出这样的大整数来,也可以由其它的整数转换而来。

Julia的乘法运算,有时候可以省略掉中间的乘号。这种方法在符号计算语言中已经实行了很久了。现在只是告诉我们,Julia也具有这种方便性而已。Julia也支持使用 ^ 来做取指的运算。但是要注意取指的优先级。如果乘法在前面,那么省略号与否,取指的优先级都是高的。但是如果是先做取指再做乘法,就不一样了。2^5x将被翻译成2^(5x),而2^5*x被翻译成(2^5)*x 。而5x^2==5(x^2)5*x^2=5*(x^2)。这说明,怎样求值不只是算术优先级的问题,与算术运算符的前后顺序也有关系。

但是需要注意的是,Julia的省略乘号的做法可能会与其编程范式产生冲突。比如(x-1)(x+2)。因为任何表达式都是返回类型的,而对一个表达式应用(),相当于把这个表达式看成一个函数(在Julia中,BigInt也是一个对象,因此有()方法;函数在Juila中使用f()来应用,但是调用的实际上是f.apply()方法),因此便会出错。这一点是我们需要注意的。一般来说,因为函数式加上面向对象的方法中,类也是一个类型,而函数正是一个具有apply方法的类型,所以就有可能与构造函数冲突。但是面向对象加上函数式比较容易解决这个问题,而如果加上省略乘号运算符,可能语法分析就出现困难了。)。在编程实践中, 面对多范式语言,必须小心语法冲突

Julia把类型也当成是值,具有一种优点,那就是我们想得到某个类型的特殊的元素的时候,可以直接通过类的方法产生,比如one(Int32)、zero(Int32)分别产生这个类型的单位元与零元,前面的typemin与typemax函数也是这样。 但是,如果我们能够通过Int32.zero这样的属性或者方法产生类型的单位元与零元的时候,相信会更方便

Julia作为科学计算语言,支持的运算符的种类是非常多的。加减乘除取指取模都是不例外的。对于除法,还有除与反除两种(视斜杠方向不同)。除此之外,还有按位运算,如~、&, >>, <<,等。具体参考http://docs.julialang.org/en/release-0.3/manual/mathematical-operations/。注意<!>是逻辑非,不是按位的反转。Julia也提供了+=这样的重新赋值操作,实际上,任何二元运算符都可以这么做。

运算符一般来说是按照类别分的,不同类别的含义不同。一般来说,分成算术运算,比较运算、逻辑运算和按位运算等几类。具有C++赋值特性的,只能是算术运算符,因为此时我们认为算术运算符返回的类型与第一个变量的类型是相同的。

Julia还支持比较运算符的累积,这是与Python相一致的,比如1<2<=3这样的表达式是有意义的。

http://docs.julialang.org/en/release-0.3/manual/mathematical-operations/里面提供了大量的运算符与类型的特殊函数(数学函数),有需要可以参考一下。

另外,Julia支持以a+b*im的形式表达复数,以及使用a//b的形式表达有理数,这些也是科学计算语言的必备的特性。(准确地说,科学计算语言的基本数据类型)

科学计算语言通常也有字符与字符串类型的支持。字符串是被当成数组对待的,因此具有中括号方法。

在Julia中,字符串可以直接引用一个变量,这时候使用美元符号引导。因此用双引号括起来的字符串,里面的字符可以被转义,也可以被求值。甚至可以在字符串中使用$name, abc, (1+2)这样的复杂的形式。这里,name被变量值替换,而1+2被求值。

Julia中的字符串不想被转义的话,可以在前面添加一个r,与Python类似。Julia的正则表达式与PCRE是兼容的。r代表的实际上是后面的字符是一个正则表达式字符串。比特数组使用b“DATA”这样的字面形式表示。另外,v“0.2”表示一个版本号类型,具有版本号的一些比较运算符,这是为了方便某些操作吧。版本号的比较机制,可以参考http://docs.julialang.org/en/release-0.3/manual/strings/

Julia的函数

Julia的函数体可以使用

function f(x,y)
    x + y
end

最后一个表达式的值,默认作为返回的值。另外,也可以使用f(x,y) = x+y来定义一个函数。此时表达式不会被求值。(注意,表达式与表达式被求值是完全不同的概念)。

此外,函数名也可以作为一个变量被赋值,比如g=f,这时候便可以用g(x,y)来调用f(x,y)。另外,函数求值实际上是调用方法,所以可以使用apply(f,2,3)。(不过,直接使用f.apply(2,3)可能是不被允许的(也许像Python那样处理的吧)。

因为Julia支持使用Unicode字符,所以有些时候语法会很奇妙,比如可以直接把Unicode字符的求和\sigma当成函数名称。

函数也可以显式添加return语句。在Julia中,运算符也是函数,所以使用+(1,2,3)以及f = + 也是被允许的。Scala也有这样的特性,但是不知道形式语言理论是怎么说的。

在Julia中,组成数组与Python中一样方便,a, b就组成了一个数组。所以表达式a,b返回的是两个表达式的和按照顺序组成一个数组。x, y = 1, 2这样的赋值语句,也是被容许的。Julia的函数也支持可选参数,做法与Python类似,此外,如果有可选参数名称,显式指定可选参数的时候,位置可以替换。

定义函数的时候发生了什么:

function f (x, y )
 x + y
end

实际上,函数的参数是在比x+y高一个作用域的位置,但是低于可见作用域。而且,参数得到值的过程是从左到右按照顺序来的。所以f(x, y = x)不会发生错误,但是f(y, x = y)却会报“变量未定义”的错误。其实我们可以看成是函数的参数是在执行之前被构造出来的过程,在动态语言中,调用f(1)相当于实际执行的是:

eval (x = 1)
// 判断出参数有一个,所以
eval (y = x)
x = y

执行中,左边的形参先得到其值,右边的形参稍后。所以,虽然直觉上可能认为函数的形参取得参数是并行的,但是其实并不是这样。

MapReduce特性

为了方便书写把一个函数作为参数传递到其它的函数,julia使用do语法,比如

map([A, B, C]) do x
    if x < 0 && iseven(x)
        return 0
    elseif x == 0
        return 1
    else
        return x
    end
end

该语句的含义是创建一个以x为参数的匿名函数,后面是该匿名函数的体。最后,do会把这个函数当成是第一个参数传递给map()函数。这与Python语言中的with语句有一些相像。

Julia的控制流可以使用(sentence; sentence; sentence)表示,小括号可以换成begin .. end。