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以外的参数值,好比,一、二、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)进行研究。java