Erlang语言精髓之一

这个世界是并行的。
如果希望将程序的行为设计得与真实世界物体的行为相一致,那么
程序就应该具有并发结构。
使用专门为并发应用设计的语言,开发将变得极为简便。
Erlang程序模拟了人类如何思考,如何交互。
——Joe Armstrong

1.=操作符(模式匹配)

当看到一个表达式像X = 123时,它的意思看似“将整数123赋予变量X”,但这种解读是不

正确的。=不是一个赋值操作符,它实际上是一个模式匹配操作符。与其他函数式编程语言一样,Erlang的变量只能绑定一次。绑定变量的意思是给变量一个值,

Erlang的变量是一次性赋值变量(single-assignment variable)。顾名思义,它们只能被赋值一次。如果试图在变量被设置后改变它的值,就会得到一个错误(事实上,你将得到的是我们刚

才看到的“badmatch”,即不匹配错误)。已被指派一个值的变量称为绑定变量,否则称为未绑定变量。一旦这个值被绑定,以后就不能改动了。

       在Erlang里,变量获得值是一次成功模式匹配操作的结果。

在大多数语言里,=表示一个赋值语句。而在Erlang里,=是一次模式匹配操作。Lhs =Rhs

的真正意思是:计算右侧(Rhs)的值,然后将结果与左侧(Lhs)的模式相匹配。

变量(比如X)是模式的一种简单形式。如之前所说的,变量只能赋值一次。我们第一次说

X = SomeExpression时,Erlang对自己说:“我要做些什么才能让这条语句为真?”因为X还没

有值,它可以绑定X到SomeExpression这个值上,这条语句就成立了

2. 变量和原子的语法

请注意Erlang的变量以大写字母开头。所以X、This和A_long_name都是变量。以小写字母

开头的名称(比如monday或friday)不是变量,而是符号常量,它们被称为原子(atom)。

如果你在任何时候看到或写出像x = 123这样的表达式(注:这里的x是小写的,如果没有

注意到的话),那么几乎可以肯定这是一个错误

 

3. 元组

如果想把一些数量固定的项目归组成单一的实体,就会使用元组(tuple)。创建元组的方法

是用大括号把想要表示的值括起来,并用逗号分隔它们。举个例子,如果想要表示某人的名字和

图灵社区会员 宝马(328827135@qq.com) 专享 尊重版权

身高,就可以用{joe, 1.82}。这个元组包含了一个原子和一个浮点数。

Erlang没有类型声明,因此要创建一个“坐标点”,只需要这么写:

这样就创建了一个元组并把它绑定到变量P上。与C结构不同,元组里的字段没有名字。因

为这个元组只包含一对整数,所以必须记住它的用途是什么。为了更容易记住元组的用途,一种

常用的做法是将原子作为元组的第一个元素,用它来表示元组是什么。因此,我们会写成{point,

10,45}而不是{10, 45},这就使程序的可理解性大大增加了。这种给元组贴标签的方式不是语

言所要求的,而是一种推荐的编程风格。

4.列表

列表(list)被用来存放任意数量的事物。创建列表的方法是用中括号把列表元素括起来,

并用逗号分隔它们。

假设想要表示一个图形。如果假定此图形由三角形和正方形组成,就可以用一个列表来表

示它。这个图形列表里的每一个元素都是固定大小的元组(例如{square,Point, Side}或者

{triangle, Point1, Point2, Point3}),但这个图形本身可能包含任意数量的事物,因此用

列表来表示

 

 

5.模块与函数

geometry.erl

-module(geometry).

-export([area/1]).

 

文件的第一行是模块声明。声明里的模块名必须与存放该模块的主文件名相同。

第二行是导出声明。Name/N这种记法是指一个带有N个参数的函数Name,N被称为函数的元

数(arity)。export的参数是由Name/N项目组成的一个列表。因此,-export([area/1])的意思

是带有一个参数的函数area可以在此模块之外调用。

未从模块里导出的函数只能在模块内调用。已导出函数就相当于面向对象编程语言(OOPL)

里的公共方法,未导出函数则相当于OOPL里的私有方法。

area函数有两个子句。这些子句由一个分号隔开,最后的子句以句号加空白结束。每条子

句都有一个头部和一个主体,两者用箭头(->)分隔。头部包含一个函数名,后接零个或更多个

模式,主体则包含一列表达式,它们会在头部里的模式与调用参

数成功匹配时执行。这些子句会根据它们在函数定义里出现的顺序进行匹配。

6.逗号、分号、句号

逗号(,)分隔函数调用、数据构造和模式中的参数。

分号(;)分隔子句。我们能在很多地方看到子句,例如函数定义,以及case、if、

try..catch和receive表达式。

句号(.)(后接空白)分隔函数整体,以及shell里的表达式。

有一种简单的方法可以记住这些:想想英语。句号分隔句子,分号分隔子句,逗号则分隔下

级子句。逗号象征短程,分号象征中程,句号则象征长程。

请注意,最后的表达式(即关键字end之前的那条)没有分号。

7.fun:基本的抽象单元

Erlang是一种函数式编程语言。此外,函数式编程语言还表示函数可以被用作其他函数的参

数,也可以返回函数。操作其他函数的函数被称为高阶函数(higher-order function),而在Erlang

中用于代表函数的数据类型被称为fun。

高阶函数是函数式编程语言的精髓——函数式程序不仅可以操作常规的数据结构,还可以操

作改变数据的函数。一旦你学会如何使用它们,就会爱上它们。在后面会看到更多的高阶函数。

可以通过下列方式使用fun。

q 对列表里的每一个元素执行相同的操作。在这个案例里,将fun作为参数传递给

lists:map/2和lists:filter/2等函数。fun的这种用法是极其普遍的。

q 创建自己的控制抽象。这一技巧极其有用。例如,Erlang没有for循环,但我们可以轻松

创建自己的for循环。创建控制抽象的优点是可以让它们精确实现我们想要的做法,而不

是依赖一组预定义的控制抽象,因为它们的行为可能不完全是我们想要的。

q 实现可重入解析代码(reentrant parsing code)、解析组合器(parser combinator)或惰性求

值器(lazy evaluator)等事物。在这个案例里,我们编写返回fun的函数。这种技术很强

大,但可能会导致程序难以调试。

funs是“匿名的”函数。这样称呼它们是因为它们没有名字。你可能会看到其他编程语言

称它们为lambda抽象。

 

 

8.关卡

关卡(guard)是一种结构,可以用它来增加模式匹配的威力。通过使用关卡,可以对某个

模式里的变量执行简单的测试和比较。假设想要编写一个计算X和Y之间最大值的max(X, Y)函

数。可以像下面这样用关卡来编写它:

max(X,Y)when X>Y -> X;

max(X,Y)-> Y

子句1会在X大于Y时匹配,结果是X。

如果子句1不匹配,系统就会尝试子句2。子句2总是返回第二个参数Y。Y必然是大于或等于

X的,否则子句1就已经匹配了。

可以在函数定义里的头部使用关卡(通过关键字when引入),也可以在支持表达式的任何地

方使用它们。当它们被用作表达式时,执行的结果是true或false这两个原子中的一个。如果关

卡的值为true,我们会说执行成功,否则执行失败。

关卡序列(guard sequence)是指单一或一系列的关卡,用分号(;)分隔。对于关卡序列G1;

G2; ...; Gn,只要其中有一个关卡(G1、G2……)的值为true,它的值就为true。

关卡由一系列关卡表达式组成,用逗号(,)分隔。关卡GuardExpr1, GuardExpr2, ... ,

GuardExprN只有在所有的关卡表达式(GuardExpr1、GuardExpr2……)都为true时才为true。

 

9.Erlang 的类型表示法

预定义类型

 

指定函数的输入输出类型

函数规范说明了某个函数的参数属于何种类型,以及该函数的返回值属于何种类型。函数规

范的编写方式如下:

-spec functionName(T1,T2,…,Tn)->Tret when

       Ti::Typei,

       Tj::Typej,

这里的T1,T2,..., Tn描述了某个函数的参数类型,Tret描述了函数返回值的类型。如果

有必要,可以在可选关键字when后面引入额外的类型变量。

我们将从一个例子入手。下面这个类型规范:

-specfile:open(FileName,Modes)->{ok,Handle}|{error,Why} when

       FileName::string(),

       Modes::[Mode],

       Mode::read|write|…

       Handle::file_handle(),

       Why::error_term.

说明如果打开FileName文件,得到的返回值不是{ok, Handle}就是{error, Why}。

FileName是一个字符串,Modes是一个由Mode组成的列表,而Mode是read、write等模式中的

 

参考gen_tcp.erl模块

-module(gen_tcp).

 

-export([connect/3,connect/4,listen/2,accept/1,accept/2,

    shutdown/2,close/1]).

-export([send/2,recv/2,recv/3,unrecv/2]).

-export([controlling_process/2]).

-export([fdopen/2]).

 

-include("inet_int.hrl").

-include("file.hrl").

-typeconnect_option() ::

        {ip,inet:socket_address()} |

        {fd,Fd :: non_neg_integer()} |

        {ifaddr,inet:socket_address()} |

       inet:address_family() |

        {port,inet:port_number()} |

       {tcp_module,module()} |

       option().

-typelisten_option() ::

        {ip,inet:socket_address()} |

        {fd,Fd :: non_neg_integer()} |

        {ifaddr,inet:socket_address()} |

       inet:address_family() |

        {port,inet:port_number()} |

        {backlog,B :: non_neg_integer()} |

       {tcp_module,module()} |

       option().

-typesocket() ::port().

 

-export_type([option/0, option_name/0, connect_option/0, listen_option/0,

             socket/0]).

 

%%

%% Connect a socket

%%

 

-specconnect(Address,Port, Options) -> {ok, Socket} | {error, Reason} when

     Address :: inet:socket_address() |inet:hostname(),

     Port :: inet:port_number(),

     Options :: [connect_option()],

     Socket :: socket(),

     Reason :: inet:posix().

 

connect(Address,Port, Opts) ->

    connect(Address,Port,Opts,infinity).

-speclisten(Port,Options) -> {ok,ListenSocket} |{error,Reason} when

     Port :: inet:port_number(),

     Options :: [listen_option()],

     ListenSocket ::socket(),

     Reason :: system_limit |inet:posix().

 

listen(Port,Opts0) ->

    {Mod,Opts} = inet:tcp_module(Opts0),

    caseMod:getserv(Port)of

    {ok,TP} ->

       Mod:listen(TP,Opts);

    {error,einval} ->

       exit(badarg);

    Other ->Other

    end.

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值