【Erlang】Erlang入门(二)

【【Erlang】Erlang入门(二)】1.模块
模块文件通常存放在以.erl为扩展名的文件中,编译成功后的文件扩展名为.beam。
2.函数
函数由多个子句构成,子句间以分号间隔,最后一条子句以句点作为结束符。
如:getArea.erl文件代码如下,

-module(getArea). -export([area/1]). %Name/N,表示一个带有N个参数的名为Name的函数 area({rectangle, Width, Height}) -> Width * Height; area({circle, R}) -> 3.14159 * R * R.

输入c(getArea). 即可编译getArea.erl文件,成功返回{ok, getArea},通过getArea:area({circle, 5}). 即可计算圆的面积。
注意:
a.当调用函数时,对其调用参数的匹配过程从第一个子句开始依次向下执行;
b.函数不能处理模式匹配失败的情形,此时程序失败后会抛出一个运行时错误。
3.目录相关
pwd(). %打印当前目录 cd("/"). %切换到/目录 ls(). %查看当前目录下的内容

4.计算商品总价
shopTotal.erl代码如下:
-module(shopTotal). -export([total/1]). total([{What, N} | T]) -> shop:cost(What) * N + total(T); total([]) -> 0.

5.fun
fun就是匿名函数,如: Double = fun(X) -> 2 * X end. erlang shell会打印#Fun<...>,这里的...是一些奇怪的数字。输入Double(4).即可调用该函数。
6.高阶函数
能够返回fun或者接收fun作为参数的函数,都被称为高阶函数。
7.以fun为参数的函数
list是标准库中的一个模块,从中导出的很多函数都是以fun作为参数的,其中最有用的是lists:map(F,L)。这个函数将fun F应用到列表L的每一个元素上,并且返回一个新的列表。
如,L = [1,2,3,4]. Double = fun(X) -> 2 * X end. lists:map(Double, L).可以得到[2,4,6,8].
另一个常用的函数是lists:filter(P,L). 它返回一个新列表,新列表由列表L中每一个满足P(E)为true的元素组成。比如,定义一个函数Even(X),当X为奇数时返回true。
Even = fun(X) -> (X rem 2) =:= 0 end.

其中,X rem 2是X对2取余,而=:=是一个恒等测试符号。
接下来测试Even分别作为map和filter的参数时的结果。
lists:map(Even, [1,2,3,4,5,6,8]).%[false,true,false,true,false,true,true] lists:filter(Even, [1,2,3,4,5,6,8]).%[2,4,6,8]

8.返回fun的参数
定义一个函数MakeTest(L),将这个列表(L)转换为一个测试函数,这个测试函数将会检查它所传入的参数是否为列表L中的成员。比如,
MakeTest = fun(L) -> (fun(X) -> lists:member(X, L) end) end.%->后面的括号中的东西就是返回值

Fruit = [apple, pear, orange]. IsFruit = MakeTest(Fruit). IsFruit(pear).

MakeTest(Fruit)并非计算一个值然后返回它,而是会返回一个函数fun(X) -> lists:member(X, Fruit),这个函数只有在被调用的时候IsFruit(pear)才会计算具体的值。
如果X是L的成员,那么函数lists:member(X,L)将返回true,反之则返回false。
通常返回fun的参数都不容易调试,但这一技术可以用来解决诸如延迟求值、可重入的解析器、解析组合子等问题,因为这些问题本身就是返回解析器的函数。
9.实现for循环
forTest.erl
-module(forTest). -export([for/3]). for(Max, Max, F) -> [F(Max)]; for(I, Max, F) -> [F(I)|for(I+1, Max, F)].

erlang shell中输入c(forTest).后,再次输入forTest:for(1,5,fun(I) -> I end).即可产生[1,2,3,4,5]。
10.列表处理
mylists.erl计算列表中所有元素之和
-module(mylists). -export([sum/1]). sum([H|T]) -> H + sum(T); sum([]) -> 0.

c(mylists).编译成功后,输入L = [1,3,4]. 接着输入mylists:sum(L). 可以得到结果8。
接下来让我们跟踪下具体的执行情况:
sum([1,3,4]) = 1 + sum([3,4]) = 1 + 3 + sum([4]) = 1 + 3 + 4 + sum([]) = 1+3+4+0 = 8

最后实现一个map/2
-module(mylists). -export([map/2]). map(_, []) -> []; map(F, [H|T]) -> [F(H)|map(F, T)].

c(mylists).编译成功后,输入L = [1,2,3,4]. 接着输入mylists:map(fun(X) -> 2 * X end, L).即可得到[2,4,6,8,10]。
重写total函数(shopFinal.erl)
-module(shopFinal). -export(total/1).%意味着函数total/1能够在模块shopFinal之外调用 -import(lists, [map/2, sum/1]). %意味着函数map/2是从lists模块中导入的,可以用map(Fun, ...) total(L) -> sum(map(fun({What, N}) -> shop:cost(What) * N end, L)).

11.列表解析
列表解析是一种无须使用fun、map或者filter来创建列表的表达式。常见形式为:[X || Qualifier1, Qualifier2,...]
X可以是任意一个表达式,每个限定词可以是一个生成器或者一个过滤器。
生成器通常写为Pattern<-ListExpr,其中ListExpr必须是一个对列表项求值的表达式。
过滤器可以是一个谓词(返回true或者false的函数),也可以是一个布尔表达式。
比如之前将列表当中的元素加倍的例子,使用列表解析的方式,可以这样写:
[2 * X || X <- L].

记号[F(X) || X <- L]代表"由F(X)组成的列表,其中X取值于列表L"。因此上述表达式意味着列表L中每一个元素X乘上2以后的列表。
再来看下元组中的列表解析,
Buy = [{apple, 3}, {pear, 2}]. [{Name, 2 * Num} || {Name, Num} <- Buy].

注意,记号(||)右边的元组{Name, Num}是用于匹配列表Buy中每个元素的模式,左边的元组{Name, 2*Num}是个构造器。
那么total函数还可以这样写:
total(L) -> lists:sum([shop:cost(What) * N || {What, N} <- L]).

列表解析能明显地缩短代码,比如map可以这样写:
map(F, L) -> [F(X) || X <- L].

12.实现快速排序
qsortTest.erl
-module(qsortTest). -export([qsort/1]). qsort([]) -> []; qsort([Pivot|T]) -> qsort([X || X <- T, X < Pivot]) ++ [Pivot] ++ qsort([X || X <- T, X >= Pivot]).

13.实现字符串所有可能的排列
permsTest.erl
-module(permsTest). -export([perms/1]). perms([]) -> [[]]; perms(L) -> [[H|T] || H <- L, T <- perms(L--[H])].

14.断言
断言(guard)是一种用于强化模式匹配的结构。如实现一个比较大小的函数max(X,Y),那么使用断言可以这样实现:
max(X, Y) when X > Y -> X; max(X, Y) -> Y.

如果第一个子句不匹配,那么尝试匹配第二个子句,此时Y必然大于等于X。
15.断言序列
断言序列可以是单个断言,也可以是一系列用分号分开的断言集合,只要任意一个断言为true,那么整个断言序列就为true。
断言也可以是一系列用逗号分开的断言集合,只有所有的断言都为true,整个断言序列才是true。

    推荐阅读