(强力建议推广Erlang)ErLang语法简介与特点分析


ErLang语法并不复杂(如果你要学习它,就要从心里这样想),他也只不过一种计算开发语言而已,和其他语言有着很多的相似或者相近的地方。我们只需要坦然面对。当然我们更要学习和牢记它和其他语言不同的地方,下面详细介绍一下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)进行研究。

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值