论坛首页 综合技术版 Erlang

ErLang语法提要

浏览 14545 次
该帖已经被评为良好帖
作者 正文
时间:2007-05-28
ErLang语法提要

ErLang中的标点符号
ErLang语法中充满了一些约定。大写字母开头的名字(比如Address),表示一个变量,包括参数、局部变量等;小写字母开头的单词(比如ok),表示一个常量,叫做atom(原子的意思),包括常量名、函数名、模块名等。
ErLang的注释用%开头。ErLang用下划线“_”表示任意变量,类似于Java的switch语法里面的default选项。
ErLang脱胎于Prolog,不过,我觉得,ErLang语法和Haskell语法比较象,都是采用 -> 定义函数。
ErLang语句中的标点符号用法很象文章的标点符号。
整个函数定义结束用一个句号“.”;同一个函数中,并列的逻辑分支之间,用分号“;”分界;顺序语句之间,用逗号“,”分隔。
ErLang中,{ }不是表示程序块的开头和结尾,而是表示一种特殊的数据结构类型——Tuple(元组),比如,{12, 3, ok}。我们可以把Tuple理解为定长数组。
[ ] 则表示最基本的函数式编程的数据结构类型——List。List数据结构很基本,写法和用法也有一定的复杂度,不是表面上看起来那么简单,后面讲解Closure的章节会详细介绍List的最基本的构造原理。
下面我们来看一个简单的例子。
我们首先定义一个最简单的函数,把一个参数乘以10,然后加1。
times10( Number ) –>
Temp = 10 * Number,
Temp + 1.

为了说明问题,上面的代码把乘法操作和加法操作分成两个步骤。Temp = 10 * Number语句后面是逗号,因为这是两条顺序执行的语句。Temp + 1语句后面是句号,表示整个函数定义结束。而且,可以看出,ErLang没有return语句,最后执行的那条语句的执行结果就是返回值。
下面,我们把这个函数优化一下。当参数等于0的时候,直接返1;否则,就乘以10,然后加1,然后返回。这时候,我们就要用到case of逻辑分支语句,相当于java的switch语句。
times10( Number ) –>
case Number of
0 -> 1;
_ ->
Temp = 10 * Number,
Temp + 1
end.

我们来仔细观察这段ErLang程序。
当Number等于0的时候,直接返回1。由于这是一条分支语句,和后面的分支是并列的关系,所以,1的后面的标点符号是分号。后面这个分支,下划线“_”表示任何其它值,这里就表示除了1之外的任何其它数值。
需要注意的一点是,case of语句需要用end结尾,end之前不需要有标点符号。
上述代码中的case of 语句,其实就是Pattern Match的一种。ErLang的Pattern Match很强大,能够大幅度简化程序逻辑,后面进行专门介绍。
Pattern Match
Pattern Match主要有两个功能——比较分派和变量赋值。
其中,比较分派是最主要的功能。比较分派的意思是,根据参数值进行条件分支的分派。可以把比较分派功能看作是一种类似于if, else等条件分支语句的简洁强大写法。
上面的例子中,case Number of 就是根据Number的值进行比较分派。更常见的写法是,可以把Pattern Match部分提到函数定义分支的高度。于是,上述代码可以写成下面的形式:
times10( 0 ) –> 1;
times10( Number ) –>
Temp = 10 * Number,
Temp + 1.

这段代码由两个函数定义分支构成,由于两个函数分支的函数名相同,而且参数个数相同,而且两个函数定义分支之间采用分号“;”分隔,说明这是同一个函数的定义。函数式编程语言中,这种定义方式很常见,看起来形式很整齐,宛如数学公式。
这段代码的含义是,当参数值等于0的时候,那么,程序走第一个函数定义分支(即分号“;”结尾的“times10( 0 ) –> 1;”),否则,走下面的函数定义分支(即“times10( Number ) –>…”)。
第二个分支中的参数不是一个常数,而是一个变量Number,表示这个分支可以接受任何除了0之外的参数值,比如,1、2、12等等,这些值将赋给变量Number。
因此,这个地方也体现了Pattern Match的第二个功能——变量赋值。

Pattern Match的形式可以很复杂,下面举几个典型的例子。
(1)数据结构拆解赋值
前面将到了ErLang语言有一种相当于定长数组的Tuple类型,我们可以很方便地根据元素的位置进行并行赋值。比如,
{First, Second} = {1, 2}
我们还可以对复合Tuple数据结构进行赋值,比如
{A, {B, C}, D} = { 1, {2, 3}, 4 }
List数据结构的赋值也是类似。由于List的写法和用法不是那么简单,三言两语也说不清楚,还徒增困扰,这里不再赘述。
(2)assertEquals语句
在Java等语言中,我们写单元测试的时候,会写一些assert语句,验证程序运行结果。这些assert语句通常是以API的方式提供,比如,assertTrue()、assertEquals()等。
在ErLang中,可以用简单的语句达到类似于assertTrue()、assertEquals()等API的效果。
比如,ErLang中,true = testA() 这样的语句表示testA的返回结果必须是true,否则就会抛出异常。这个用法很巧妙。这里解释一下。
前面讲过,ErLang语法约定,小写字母开头的名字,都是常量名。这里的true自然也是一个常量,既然是常量,我们不可能对它赋值,那么true = testA()的意思就不是赋值,而是进行匹配比较。
(3)匹配和赋值同时进行
我们来看这样一段代码。
case Result of
{ok, Message} -> save(Message);
{error, ErrorMessage} -> log(ErrorMessage)
end.

这段代码中,Result是一个Tuple类型,包含两个元素,第一个元素表示成功(ok)或者失败(error),第二个元素表示具体的信息。
可以看到,这两个条件分支中,同时出现了常量和变量。第一个条件分支中的ok是常量,Message是变量;第二个条件分支中的error是常量,ErrorMessage是变量。
这两个条件分支都既有比较判断,也有变量赋值。首先,判断ResultTuple中的第一个元素和哪一个分支的第一个元素匹配,如果相配,那么把ResultTuple中的第二个元素赋给这个分支的第二个变量元素。即,如果Result的第一个元素是ok,那么走第一个条件分支,并且把Result的第二个元素赋给Message变量;如果Result的第二个元素是error,那么走第二个条件分支,并且把Result的第二个元素赋给ErrorMessage变量。

在Java等语言中,实现上述的条件分支逻辑,则需要多写几条语句ErLang语法可以从形式上美化和简化逻辑分支分派复杂的程序。
除了支持数相等比较,Pattern Match还可以进行范围比较、大小比较等,需要用到关键字when,不过用到when的情况,就比if else简洁不了多少,这里不再赘述。
匿名函数
ErLang允许在一个函数体内部定义另一个匿名函数,这是函数式编程的最基本的功能。这样,函数式语言才可以支持Closure。我们来看一个ErLang的匿名函数的例子。
outer( C ) –>
Inner = fun(A, B) -> A + B + C end,
Inner(2, 3).

这段代码首先定义了一个命名函数outer,然后在outer函数内部定义了一个匿名函数。可以看到,这个匿名函数采用关键字fun来定义。前面讲过,函数式编程的函数就相当于面向对象编程的类实例对象,匿名函数自然也是这样,也相当于类实例,我们可以把这个匿名函数赋给一个变量Inner,然后我们还可以把这个变量当作函数来调用,比如,Inner(2, 3)。
fun是ErLang用来定义匿名函数的关键字。这个关键字很重要。fun定义匿名函数的用法不是很复杂,和命名函数定义类似。
函数分支的定义也是类似,只是需要用end结尾,而不是用句号“.”结尾,而且fun只需要写一次,不需要向命名函数那样,每个分支都要写。比如,
MyFunction = fun(0) -> 0;
(Number) -> Number * 10 + 1 end,
MyFunction(3),
函数作为变量
匿名函数可以当作对象赋给变量,命名函数同样也可以赋给变量。具体用法还是需要借助重要的fun关键字。比如,
MyFunction = fun outer / 1

就可以把上述定义的outer函数赋给MyFunction变量。后面的 / 0表示这个outer函数只有一个参数。因为ErLang允许有多个同名函数的定义,只要参数个数不同,就是不同的函数。
我们可以看到,任何函数都可以作为变量,也可以作为参数和返回值传来传去,这些变量也可以随时作为函数进行调用,于是就具有了一定的动态性。
函数的动态调用
ErLang有一个apply函数,可以动态调用某一个函数变量。
基本用法是 apply( 函数变量,函数参数列表 )。比如,上面的MyFunciton函数变量,就可以这么调用,apply( MyFunction, [ 5 ])。
那么我们能否根据一个字符串作为函数名获取一个函数变量呢?这样我们就可以根据一个字符串来动态调用某个函数了。
ErLang中,做到这一点很简单。前面讲过,函数名一旦定义了,自然就固定了,这也类似于常量名,属于不可变的atom(原子)。所有的atom都可以转换成字符串,也可以从字符串转换过来。ErLang中的字符串实质上都是List。字符串和atom之间的转换通过list_to_atom和atom_to_list来转换。
于是我们可以这样获取MyFunciton:MyFunction = list_to_atom(“outer”)
如果outer函数已经定义,那么MyFucntion就等于outer函数,如果outer函数没有定义,那么list_to_atom(“outer”)会产生一个新的叫做outer的atom,MyFucntion就等于这个新产生的atom。
如果需要强制产生一个已经存在的atom,那么我们需要调用list_to_existing_atom转换函数,这个函数不会产生新的atom,而是返回一个已经存在了的atom。
Tuple作为数据成员集合
前面讲解函数式编程特性的时候,提到了函数式编程没有面向对象编程的成员变量,这是一个限制。
ErLang的Tuple类型可以一定程度克服这个限制。Tuple可以一定程度上担当容纳成员变量的职责。
面向对象的类定义,其实就是一群数据和函数的集合,只是集合的成员之间都有一个this指针相关联,可以相互找到。
ErLang的Tuple类型就是数据的集合,可以很自然地发挥成员变量的作用,比如,{Member1, Member2}。
读者可能会说,ErLang的函数也可以作为变量,也可以放到Tuple里面,比如, { Memer1, Member2, Funtion1, Function2}。这不就和面向对象编程一样了吗?
遗憾的是,这样做是得不偿失的。因为函数式编程没有面向对象的那种内在的this指针支持,自然也没有内在的多态和继承支持,硬把数据和函数糅合在一个Tuple里面,一点好处都没有,而且还丧失了函数作为实例对象的灵活性。
所以,函数式编程的最佳实践(Best Practice)应该是:Tuple用来容纳成员数据,函数操作Tuple。Tuple定义和函数定义加在一起,就构成了松散的数据结构,功能上类似于面向对象的类定义。Tuple + 函数的数据结构,具有多态的特性,因为函数本身能够作为变量替换;但是不具有继承的特性,因为没有this指针的内在支持。
正是因为Tuple在数据类型构造方面的重大作用,所以,ErLang专门引入了一种叫做Record的宏定义,可以对Tuple的数组下标位置命名。比如,把第一个元素叫做Address,第二个元素叫做Zipcode,这样程序员就可以这些名字访问Tuple里面的元素,而不需要按照数组下标位置来访问。
Tuple和Record的具体用法还是有一定复杂度,限于篇幅,本章没有展开说明,只提了一些原理方面的要点。
其它
ErLang还有其它语法特性和细节,不再一一赘述。有兴趣的读者,可以自行去ErLang网站(www.erlang.org)进行研究。
   
时间:2007-05-28
讲语法的,除了给你投个票以外,好像没什么好回复的,就问个有点不相关的问题吧。

"erlang脱胎于prolog",erlang是否适合做推理问题?如果不适合,它比起prolog少了哪些特性导致它不适合?FP语言我感觉相互之间差别也满大的,共同点就是看了头大,erlang算是慢慢有点习惯了。有兴趣做个各种FP语言的比较和介绍吗?整个论坛各种FP语言都深入过的也就你们几位老大了
   
0 请登录后投票
时间:2007-05-28
qiezi 写道
讲语法的,除了给你投个票以外,好像没什么好回复的,就问个有点不相关的问题吧。

"erlang脱胎于prolog",erlang是否适合做推理问题?如果不适合,它比起prolog少了哪些特性导致它不适合?FP语言我感觉相互之间差别也满大的,共同点就是看了头大,erlang算是慢慢有点习惯了。有兴趣做个各种FP语言的比较和介绍吗?整个论坛各种FP语言都深入过的也就你们几位老大了
这边有个比较 haskell和erlang的

http://wagerlabs.com/2006/1/1/haskell-vs-erlang-reloaded/
   
0 请登录后投票
时间:2007-05-28
偶觉得你这篇关于erlang语法的帖子写的即琐屑又凌乱,有些地方对erlang语法的理解有些误区

erlang跟prolog语言没啥关系,joe armstrong花了几年的时间来找他心目中满意的能符合他要求的语言,最后打算改造prolog来实现他的想法,可是到后面应该是完全不一样了吧.

至于说erlang跟haskell语法相似也谈不上,语法上他们太多不一样了,比如最简单的元祖,列表,函数定义语法就完全不一样,还有haskell太复杂了,而erlang简单的多.

还有一个需要特别注意的是,erlang根本没有赋值的概念,只有匹配和绑定.erlang中的 = 号不要理解为赋值,在erlang中,= 的意思是匹配,就是=的左值和右值是相匹配的,不匹配就会报错,所以true = assertA()就是true表达式值匹配assertA()表达式的值.如果assertA()求值结果不是true,那么自然匹配失败.

我想到这么多,可能有谬误,还请指正.
   
0 请登录后投票
时间:2007-05-28
现在写程序有时候感觉到一种语言如果没有模式匹配,实在是太太太太麻烦了
   
0 请登录后投票
时间:2007-05-28
potian 写道
现在写程序有时候感觉到一种语言如果没有模式匹配,实在是太太太太麻烦了


同感。

Joel Reymont刚把OCaml和Haskell做了一个比较,看来Haskell目前还是太学院:
http://wagerlabs.com/
说来也巧,他现在想做的事情和我想做的差不多(证券交易),只是我现在的选择是:计算密集和图形密集任务用Java,高并发用Erlang。Java虽然没有模式匹配,但性能方面还是足够了,而且有大把的现存开源库。当然Joel是想所有东西都在服务器端跑,估计以后图形也是服务器端生成再在Web呈现给用户。我的想法不同。
   
0 请登录后投票
时间:2007-05-28
Erlang 写道
偶觉得你这篇关于erlang语法的帖子写的即琐屑又凌乱,有些地方对erlang语法的理解有些误区。
erlang跟prolog语言没啥关系,joe armstrong花了几年的时间来找他心目中满意的能符合他要求的语言,最后打算改造prolog来实现他的想法,可是到后面应该是完全不一样了吧.
至于说erlang跟haskell语法相似也谈不上,语法上他们太多不一样了,比如最简单的元祖,列表,函数定义语法就完全不一样,还有haskell太复杂了,而erlang简单的多.
还有一个需要特别注意的是,erlang根本没有赋值的概念,只有匹配和绑定.erlang中的 = 号不要理解为赋值,在erlang中,= 的意思是匹配,就是=的左值和右值是相匹配的,不匹配就会报错,所以true = assertA()就是true表达式值匹配assertA()表达式的值.如果assertA()求值结果不是true,那么自然匹配失败.
我想到这么多,可能有谬误,还请指正.


我觉得这篇写得挺好。目前,在国内 Erlang 的中文资料极其稀少,这篇应该能够帮到很多人认识和了解 Erlang 语言。我已经向楼主发了站内消息,申请在 erlang-china.org 进行转载。

Erlang 和 Prolog 有没有关系,这个问题要看你怎么来定义“关系”这个词了。下面这些内容来自于 Joe Armstrong 的文章:
引用
......By 1990 the JAM machine was working well and had surpassed the original goal of being seventy times faster than the original Prolog interpreter. Erlang now had its own syntax (up to now it could be regarded as a dialect of Prolog) and could be regarded as a language in its own right, rather than as a dialect of Prolog......

我认为,说“ erlang 跟 prolog 语言没啥关系”,这样的提法是不合适的。

对其他的 fp 不太了解,就不发表意见了。
   
0 请登录后投票
时间:2007-05-28
dcaoyuan 写道
只是我现在的选择是:计算密集和图形密集任务用Java,高并发用Erlang。Java虽然没有模式匹配,但性能方面还是足够了,而且有大把的现存开源库。当然Joel是想所有东西都在服务器端跑,估计以后图形也是服务器端生成再在Web呈现给用户。我的想法不同。

计算密集和图形密集任务适合用Java?
我见识少,没有听说Java有那些高效的数值计算和图形处理库。
而且怀疑Java在运算过程中产生大量小型对象效率会比较低。
   
0 请登录后投票
时间:2007-05-28
hyf 写道
dcaoyuan 写道
只是我现在的选择是:计算密集和图形密集任务用Java,高并发用Erlang。Java虽然没有模式匹配,但性能方面还是足够了,而且有大把的现存开源库。当然Joel是想所有东西都在服务器端跑,估计以后图形也是服务器端生成再在Web呈现给用户。我的想法不同。

计算密集和图形密集任务适合用Java?
我见识少,没有听说Java有那些高效的数值计算和图形处理库。
而且怀疑Java在运算过程中产生大量小型对象效率会比较低。

最基础古老的运算库是Fortran写的,比如线性代数的LAPACK库
后来的大部分是C++写的,也有少数OCaml写的例子。
不过dcaoyuan说的只是密集而非大规模,够快就可以了。

因为项目的关系,我对Joel的工作也很感兴趣,他在做的似乎属于程式交易(algorithmic trading)系统。
   
0 请登录后投票
时间:2007-05-28
potian 写道
现在写程序有时候感觉到一种语言如果没有模式匹配,实在是太太太太麻烦了

前阵子Haskell社区讨论使用view pattern来增强模式匹配的建议,意思是现在的模式匹配就地匹配破坏抽象层次,而应该把模式也抽象成函数表达。F#动作更快,最新版本已经实现了类似作用的active pattern
   
0 请登录后投票
论坛首页 综合技术版 Erlang

跳转论坛:
JavaEye推荐