Erlang学习笔记/快速入门#
Erlang比较神秘,小众。函数式编程。主要适用于服务器端长连接、高并发处理,最初是爱立信针对电信系统专门设计,公认很稳定、容错好,广泛用于通信系统。
很多思想现在看来非常超前并且仍然超前并且不会过时。
各种原因导致不流行(函数式编程不直观,思维方式跟主流语言有差别,不善推广等)
Whatsapp用了大量erlang 国内有很多网游公司用了erlang。阿里内部有用(https://www.zhihu.com/question/46442333)。 我们常用的rabbitmq和emq都是erlang做的。
看下来至少在保持住大量长连接方面比较靠谱,还天生支持”协程“、集群、分布式。内置分布式数据库。
语法、思想确实需要适应。有的写法非常简明,有的写法就像数学公式,非常优美,比如三五行代码就可以实现一个简单的快速排序。
嗯。有必要学习一下。
书籍#
<< Programming Erlang.2Ed >>
erlang发明者的书<< Designing for Scalability with ErlangOTP Implement Robust, Fault-Tolerant Systems>>
深入学习。otp等。<< Erlang and OTP in Action>>
otp<< Introducing Erlang Getting Started in Functional Programming>>
内容少。可快速学习或当作参考<< learn_you_some_erlang_for_great_good>>
结构类似第一本。可作参考。
安装erlang运行环境#
https://www.erlang.org/downloads 官网下载erlang环境。目前版本是OTP 23。安装后可用erlang shell直接编译和运行erlang代码。
简单的命令可用erlang shell。 复杂的可用ide。后续会介绍。
官网 https://www.erlang.org/ 在线文档 https://erldocs.com/
windows环境变量path里添加erlang的bin目录
基本概念#
注释
%
号后面是注释句号 命令结束时用英文句号.结尾。
X = 20.
变量 大写字母或_开头的字符串表示一个变量。变量只能赋值(绑定)一次。之后不能再赋值,否则报错。
X = 20. % 绑定了20
如果再输X = 20.
会报错
atom 小写字母开头的字符串。可以看作一个标签。常用于各种匹配。
多元组tuple
P = {2, gg, {name, 3}}.
PP = {k, P}
可嵌套。其中gg, name, k都是atom。P, PP是tuple变量,绑定了相应的值。 可以反向取出tuple中的值。
{P1, P2, P3} = P. % 这样P1绑定了2,P2绑定了gg,P3绑定了{name, 3}
列表List。跟数组一个意思,可存数字,atom,tuple,list等。
A = [{a, 2}, 3, b]
取出list中的数据 可以用
[HEAD|TAIL]
的形式去匹配list。|左边的HEAD会匹配开头的一个元素,TAIL会匹配剩下的list。 例如
L = [1,2,3,4,5,6].
[L1|L2] = L. % L1为1,L2为[2,3,4,5,6]。注意L2还是一个list
[L3,L4|L5] = L. % L3为1,L4为2,L5为[3,4,5,6]。
字符串
Name = "Shagua". % 绑定字符串
[83,117,114,112,114,105,115,101]. % 会输出"Surprise"
B = "信仰" % 会输出[20449,20208]
这里会有各种转换,默认貌似是utf8编码。
模式匹配 erlang里充满了匹配。
{X, abc} = {123, abc}. % 成功 X绑定了123
{A, B, C} = {1, 2, "ggg"}. % 成功
{AA, BB, CC} = {{aa, a}, bb, "cc"}. % 成功
{AAA, BBB} = {"a", "b", cc}. % 失败 ”形状“不一致
[H|T] = [1,2,3,4]. % 成功 H=1, T= [2,3,4]
[A, B, C | T] = [a, b, c, d, e, f]. % 成功 A = a, B = b, C = c, T = [d, e, f]
{GG, abc} = {123, gg}. % 失败 abc和gg两个atom匹配不了
模块和函数#
开始用ide#
ide用intellij idea社区版。 https://www.jetbrains.com/idea/download/
ide的erlang插件。本来可以在ide里装,但是被墙。只能下载后本地安装。 https://github.com/ignatov/intellij-erlang
打开ide,配置plugin,选从硬盘安装上面的插件。
新建erlang项目,src目录下新建Erlang File,选Empty module,指定模块名test。看到有几行默认代码。
改一下代码:
-module(test). % 指定这个erlang模块名字是test
-export([gg/0]). % 表示这个模块会暴露一个接口gg,斜杠后面是参数个数,0就是0个参数。
gg()->
io:format("gg~n").
定义一个函数gg,没有参数。 io是erlang自带的io库。format可打印字符。~n是换行。
ide里run。看到打印出gg。这样环境就ok了。
以后每次新学一个内容可以新建一个module,在ide中右键recompile,然后在test中调用。 也可以盯着一个module改代码。
run的配置#
打开Edit Configuration,可修改运行配置。 可以看到Module and function 默认生成了test gg。 即运行test模块的gg函数。 以后要运行多个模块多个函数的话需要自行添加和修改这个config。
函数的例子1:#
-module(geometry).
-export([area/1]).
area({rectangle, Width, Height}) ->
Width * Height;
area({square, Side}) ->
Side * Side.
同样的函数名area有两个,参数的匹配模式不一样,可以共存。注意中间是用;分隔的。
调用方式:
A = area({rectangle, 3, 4}).
会匹配第一个定义(两个rectangle匹配)
B = area({square, 12}).
会匹配第二个定义(两个square匹配)
可以进行扩展
area({circle, Radius}) -> 3.14159 * Radius * Radius.
函数的例子2:#
erlang没有直接的循环。可以这样做一个循环:
for(Max, Max, F) -> [F(Max)];
for(I, Max, F) -> [F(I)|for(I+1, Max, F)].
从第二个函数开始。对I调用F,作为list的第一个元素,再把I+1,再次调for,把返回接到list后面。 如果I增加到与Max相同,则匹配到第一个for,返回F(I)。 这样实现了对I到Max都调用F,把结果组成一个list。
函数的例子3:#
sum([H|T]) -> H + sum(T);
sum([]) -> 0.
非空list匹配第一个sum,取出第一个元素H,对于list后续部分调用sum,最后返回两者的和。 到最后T为空时,返回0,全过程结束。 这样实现了求list的和。
函数的例子4:#
list可以做这样的变换
L = [1,2,3,4,5].
[2*X || X <- L ]. % [2,4,6,8,10]
这样可以对list每个元素进行操作,得到新的list。
qsort([]) -> [];
qsort([Pivot|T]) ->
qsort([X || X <- T, X < Pivot])
++ [Pivot] ++
qsort([X || X <- T, X >= Pivot]).
这个例子实现快速排序。快速排序主要思想时分而治之。 简单说是取数组中一个元素A,把比它小的放左边,把比它大的放右边,这样A的位置必然是正确的,然后把左右部分都这样递归操作下去,最后就能排好序。 当然这里只是一个简单的实现,现实实现要考虑各种情况、初始化数据,如何取A等。
[Pivot|T]
这个参数就把第一个元素当作了标点。其余的为T
[X || X <- T, X < Pivot]
把T中所有小于Pivot的元素做成一个list,并对其qsort。[Pivot]
[X || X <- T, X >= Pivot]
把T中所有大于Pivot的元素做成一个list,并对其qsort。 把123接起来就得到了排好序的list。
case功能#
就是根据匹配,分情况处理。
case Expression of
Pattern1 -> Expr_seq1;
Pattern2 -> Expr_seq2;
...
end
fall_velocity(Planemo, Distance) ->
case Planemo of
earth -> math:sqrt(2 * 9.8 * Distance);
moon -> math:sqrt(2 * 1.6 * Distance);
mars -> math:sqrt(2 * 3.71 * Distance) % no closing period!
end.
二进制数据和位操作#
<<5,10,20>>.
双尖括号包起来就是字节串。里面每个元素值必须在0-255之间。否则会被截取后8位。
按位拼字节
X = 1.
Y = 2.
Z = 233.
MM = <<X:3, Y:1, Z:2>>.
MM. % 得到 <<9:6>>
XYZ分别取最后3位1位2位再接起来得到1001即9。6bit。
FF = <<X:3, Y:1, Z:5>>.
FF. % 得到<<36,1:1>>
233 = 0b11101001 % 最后5位拿出来。前四位拼到前一个字节,最后剩一个1。
0b00100100,0b1
按位取字节
<<A:5, B:1, C:3>> = FF.
A得到0b00100。B得到0b1。C得到0b1 取的时候总长度要匹配,否则报错。
解析和拼装协议数据可能会非常方便。
其他复杂用法待学习。。
Erlang进程#
process是erlang的核心概念。 erlang的进程是在erlang虚拟机层面实现的,不是操作系统层面的进程。可与协程类比。
shell里输入
self().
返回<0.102.0>这样的数据,为当前进程的标识,即Pid。
基本概念:#
process的创建和销毁非常快 process间传消息非常快 process跨平台,在所有平台表现一致 可以同时存在大量的process process不共享内存,是完全独立的。 process间通信的唯一方式是传消息
基本操作1:#
创建一个process,使用Mod模块的Func函数,参数为Args。返回Pid
Pid = spawn(Mod, Func, Args).
基本操作2:#
发Message给Pid
Pid ! Message.
基本操作3:#
收数据
receive
Pattern1 ->
Expressions1;
Pattern2 ->
Expressions2;
...
end
现在可以做一个简单的服务。#
起一个server.erl
-module(server).
-author("xc").
-export([loop/0]). % 导出loop接口
loop()->
io:format("loop~n"),
receive % 接收数据
{From, {rectangle, Width, Height}}-> % 匹配长方形参数
From ! Width * Height, % 发送结果到From
loop(); % 再次开始收数据。通常这样递归会不断消耗栈空间,最后溢出。erlang有尾递归优化,不会重复消耗栈。
{From, {circle, R}}-> % 匹配圆形参数
io:format("circle~n"),
From ! 3.14159 * R * R,
loop();
{From, Other}-> % 匹配其他
From ! {error, Other},
loop()
after 5000 -> % 5秒超时会走到这里
io:format("timeout 1~n"),
loop()
end.
起一个test.erl,启动server的loop,并给它发消息。
-module(test).
-author("xc").
-export([test_1/0]). % 导出test_1接口
-import(server, [loop/0]). % 导入loop接口
test_1 ()->
Pid = spawn(server, loop, []), % 创建一个process,运行server模块的loop函数。得到Pid
Pid ! {self(), {circle, 12}}, % 发消息给Pid。消息是{自身Pid, {circle, 12}},匹配到loop中的{From, {circle, R}}
receive
Result ->
io:format("~p~n", [Result])
after 3000->
io:format("after 3000")
end.
可以把某个pid和atom绑定,变的好读好找。以后直接给这个atom发消息就行了。
Pid1 = spawn(bounce,report,[1]).
register(bounce,Pid1).
bounce ! hello.
另外erlang自带一个监控页面。有当前节点的各种重要数据,包括mnesia数据库。还可以进行相关操作。
observer:start().
数据存储#
tuple到record
-record(planemo, {name, gravity, diameter, distance_from_sun}).
map
Planemos = #{ earth => 9.8, moon => 1.6, mars => 3.71 }.
maps:get(moon, Planemos). % 取
ETS: Erlang Term Storage 一种erlang数据表
有四种形式
set
同个key只能存一个值ordered set
同个key只能存一个值。key有序bag
同个key可以存多个值。同个key下的值不能重复。duplicate bag
同个key可以存多个值。同个key下的值可以重复。创建表
T = ets:new(my_table, [ordered_set])
插入数据
ets:insert(T, {gg, {gt, "4wf", <<56>>}}).
observer:start().
可看数据取数据
ets:lookup(T, gg).
删除数据
ets:delete(TT, gg).
删除表
ets:delete(TT).
mnesia(希腊语memory) erlang自带的数据库。轻量快速、分布式、可同步、支持事务。 可直接存在内存。可直接存erlang的record。 不适用于大规模数据(虽然有成功案例),非常适合小规模应用、集群节点间维护一致的数据等。
分布式相关#
erlang自带分布式方案 https://www.erlang.org/doc/reference_manual/distributed.html
程序发布#
www.rebar3.org 是erlang常用的打包、发布工具。
下载rebar3
按https://www.rebar3.org/docs/getting-started windows上在rebar3文件同个目录建一个rebar3.cmd
填入
@echo off
escript.exe "%~dpn0" %*
保存。再cmd运行rebar3 –help。可正常运行即可。
创建新app
rebar3 new app myapp
编译 rebar3两个文件复制进目录
rebar3 compile
创建新release
rebar3 new release myrelease
erlang版本管理#
erlang otp会持续进化、发布新版本。那么如果需要测试、对比、升级的话,需要不同版本共存。
用kerl可以管理erlang版本 https://github.com/kerl/kerl
Elixr#
原版erlang还是比较晦涩,有诸多不便。 Elixr在erlang基础上发展和改进。沿用erlang的vm。 把erlang的语法和otp改的更舒服。 另外扩展了多种工具、功能(模板、测试、打包发布)。