1. 传参或在匿名函数内慎用self()

通常在做消息传递或新建进程的时候我们需要将当前进程的Pid发给目标进程以便接收返回信息,但初学者不留意容易犯以下错误

spawn(fun() ->
loop(self(), gen_tcp:accpet(...))
end).

fun这段代码在本进程内是不会预先执行的,代码会原封不动传给目标进程。当实际调用self()的时候,获取的实际不是本进程的Pid了。

所以建议当需要传递当前进程Pid或者其他当前进程类似函数的时候,先求值再传递。保持良好习惯就可以避免类似的坑。

Pid = self(),
Socket = gen_tcp:accpet(...),
spawn(fun() ->
loop(Pid, Socket)
end).

2. Message Passing支持发送Socket、文件句柄等对象引用消息

稍微了解Erlang的同学都知道Erlang消息传递目标进程接收的实际是发送进程消息的一个拷贝副本,对于存储类数据这个没有问题。

当对于Socket,FileHandler这类对象的时候,事实上也是可以传递并能起到预期效果的。

(以下Demo代码来自Erlang的Mailing List)

%This is an example of how trivial writing a FTP-like client in Erlang.

%On machine 1.
File = file:open(FileName, [read]).
PidOfProcessOnMachine2 ! {remote_file, File}. %On machine 2.
receive
{remote_file, File} ->
{ok, Data} = file:read(File, 1000000),
write_to_local_disk(Data)
end

3. 在不需追踪函数状态的时候,尽量使用尾递归

众所周知,Erlang是没有循环结构的,我们要想实现循环基本是靠递归实现的。但又众所周知,函数递归是要不停压栈的,我们要想实现 while(true)怎么办?

答案就是尾递归,许多这类语言都已在编译器层面实现了尾递归优化,在编译器识别出尾递归后,编译器会直接丢掉当前函数状态信息变成做跳转,这里汇编层面其实就和循环结构很类似了。

但尾递归也有个缺点,就是函数状态没法保留了,所以有时候复杂情况需要追踪调试函数栈状态的时候就不能用尾递归。但其实在Erlang这类函数式语言里,尾递归大多数情况是用来实现循环,所以实际上也不需要留意函数栈。

4. 动态创建原子要十分谨慎

Erlang里有一种特殊的结构叫做原子(atom),一般用来做类似传统语言里的常量作用或用来辅助模式匹配。

Erlang默认有几百万个内置原子,但是Erlang除非整个VM退出了,否则是不会对任何原子做GC的。

所以有时候我们为了匹配会使用 list_to_atom 函数去动态创建原子,但这是很危险的,甚至有可能导致内存泄露。

所以除非你自己很清醒自己的所作所为并且考虑到别人对你代码的调用情况,否则尽量不要动态创建原子。

但静态创建原子是没问题的,毕竟你手动打的数目总是极其有限,比如:

receive
{my_own_atom, Var} -> pass.
end.

但谨慎出现以下代码:

% On machine 1

Type = recv(),
Msg = recv(),
Machine2 ! {list_to_atom(Type), Msg}. % On machine 2 receive
{type1, Msg} -> pass;
{type2, Msg} -> pass
end.

这里的Type很容易成为受攻击的对象。

5. 巧用原子作模式匹配

经常我们要解析协议或者匹配同等元素个数的元组列表,这时候可以用原子来进行区分实现模式匹配。

% Receiver
Packet = recv(Socket), % Get TCP Packet case Packet of
[new_user, "||", Username] ->
pass;
[new_board, "||"] ->
pass.
end. % Sender send([new_user, "||", Username]).

6. 特殊字符使用$开头,如$\n。

这个没什么好说,有需要去查看对应手册。

Line = io:get_line("Input: "),
S = string:strip(Line, both, $\n).

7. 大型数据慎用消息传递,有必要可考虑ETS表做进程共享

Erlang有个口号叫“小消息 大计算”,所以使用Erlang期望传递的消息本来就是小消息。但我们经常也不可避免需要进程间共享一些大消息,这时候我们可以考虑从借助进程字典或ETS表。但其实Erlang的这种进程间只能通过MP通信的机制也逼迫我们在设计程序时要求每个进程的角色分工很明确。

8. 跨机器间的进程消息发送要小心,小型消息频率过高TCP头部的发送代价也高。

这个其实还是见仁见智,根据实际业务情况而定。如果真的是因为TCP消耗引起的性能问题,就要考虑本地开个进程做代理,缓存一定的消息,定时批量发送。其实就是做个缓存队列。

可参见此文:Erlang中频繁发送远程消息要注意的问题

9. 函数传参记得参数相对应

这个没啥好说的,直接看代码。尽管fun匿名函数处理不需要参数,但for_内的F是个一元函数,所以也要至少用个_匹配符去代表那里有个参数。Erlang有另一种元调用方式apply(有点像JavaScript),apply传参是用个列表Args = [...],适用于较灵活的一些调用,比如spawn,但这种反射式调用一般效率都会较低,Erlang的apply据说比本地直接调用F()要慢上6 - 10倍。

for_(I, Max, F) ->
case I == Max of
false -> [F(I) | for_(I + 1, Max, F)];
true -> [F(Max)]
end. for_(0, 5, fun(_) ->
io:write("Hello Erlang")
end).

10. 列表操作要谨慎

很多有其他类函数式语言(Python、Coffee等)经历的同学会很喜欢列表结构,但是Erlang的列表相对比较不同。

首先,Erlang的所有“变量”都是不可变也不可二次绑定的,所以想像Python那样自由操作list是不可能的,必须每次修改返回一个新变量。

列表右侧增长: [1,2,3] ++ [4,5] = [1,2,3,4,5],   "Hello" ++ " " ++ "Erlang" = "Hello Erlang"

++其实是lists:append/2的syntax sugar,切记和动态创建原子一样,可偶尔为之,但不要放任列表动态右侧增长。++会复制左边的元素,会使复制多次,最后导致平方倍的复杂度。

列表左侧增长:[1,2,3 | [4,5]] = [1,2,3,4,5]

不要和 [[1,2,3] | [4,5]] = [[1,2,3],4,5] 搞混。

列表length方法是O(N)时间复杂度,慎用length(List),很多需求可以用模式匹配实现。

case List of
[Elem | _] -> process(Elem);
[] -> processEmptyList()
end.

但是元组和二进制串的size方法却是O(1)的,可放心使用。

Erlang 初学者技巧及避免的陷阱的更多相关文章

  1. Delphi初学者应小心的六大陷阱

    Delphi初学者应小心的六大陷阱   作者:子夜编译       初学DelphiI的人,由于各种原因,对DelphiI中的许多概念不能很好的理解,并由此带来了许多的问题,或者是开发出的程序稳性不好 ...

  2. JavaScript中8个常见的陷阱

    译者按: 漫漫编程路,总有一些坑让你泪流满面. 原文: Who said javascript was easy ? 译者: Fundebug 为了保证可读性,本文采用意译而非直译.另外,本文版权归原 ...

  3. 《Erlang程序设计(第2版)》

    <Erlang程序设计(第2版)> 基本信息 作者: (瑞典)Joe Armstrong 译者: 牛化成 丛书名: 图灵程序设计丛书 出版社:人民邮电出版社 ISBN:9787115354 ...

  4. 如何写复杂的SQL

    经常有人问我那非常复杂的sql是怎么写出来的,我一直不知道该怎么回答.         因为虽然我写这样的sql很顺手,可是我却不知道怎么告诉别人怎么写. 很多人将这个问题归结为天赋,我却不这么看,我 ...

  5. JavaScript常用,继承,原生JavaScript实现classList

    原文链接:http://caibaojian.com/8-javascript-attention.html 基于 Class 的组件最佳实践(Class Based Components) 基于 C ...

  6. JavaScript中8个容易犯的错误

    这里dbestech针对JavaScript初学者给出一些技巧和列出一些陷阱. 1. 你是否尝试过对数组元素进行排序? JavaScript默认使用字典序(alphanumeric)来排序.因此,[1 ...

  7. Golang核心编程

    源码地址: https://github.com/mikeygithub/GoCode 第1章 1Golang 的学习方向 Go 语言,我们可以简单的写成 Golang 1.2Golang 的应用领域 ...

  8. C和C指针小记(十五)-结构和联合

    1.结构 1.1 结构声明 在声明结构时,必须列出它包含的所有成员.这个列表包括每个成员的类型和名称. struct tag {member-list} variable-list; 例如 //A s ...

  9. 如何做Go的性能优化?(转)

    Go的性能优化其实总的来说和C/C++等这些都差不多,但也有它自己独有的排查方法和陷阱,这些都来源于它的语言特性和环境. 1.性能优化前提——任何好的东西都是在正确的前提上 代码界的很多事是和我们生活 ...

随机推荐

  1. Springboot快速入门创建

    maven构建项目 1.访问http://start.spring.io/,进入快速创建工程的主页 可参考下图所示: 2.选择构建工具Maven Project.Spring Boot版本1.3.6以 ...

  2. C#操作XML总结

    1.using System.Xml; using System.Xml; //初始化一个xml实例 XmlDocument xml=new XmlDocument(); //导入指定xml文件 xm ...

  3. lvs+keepalived

    一.简介 VS/NAT原理图: 二.系统环境 实验拓扑: 系统平台:CentOS 6.3 Kernel:2.6.32-279.el6.i686 LVS版本:ipvsadm-1.26 keepalive ...

  4. jquery简单的轮播效果!

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http ...

  5. 在 Angularjs 中 ui-sref 和 $state.go 如何传递参数

    1 ui-sref.$state.go 的区别 ui-sref 一般使用在 <a>...</a>: <a ui-sref="message-list" ...

  6. octave手册

    GNU OCTAVE是一种高级语言,主要用于数值计算.它提供交互式命令行窗口,用于求解线性和非线性问题并计算出数值,并可以进行其它数值实验,还可以用来作为一个批量数据处理语言 运行Ocatve: oc ...

  7. 网站开启https后加密协议始终是TLS1.0如何配置成TLS1.2?

    p { margin-bottom: 0.1in; line-height: 120% } 网站开启https后加密协议始终是TLS1.0如何配置成TLS1.2? 要在服务器上开启 TLSv1.,通常 ...

  8. android include进来的组件 调用其子元素

    include标签包裹着一个可复用的布局: <include layout="@layout/footer_detail" android:id="@+id/foo ...

  9. Eclipse中使用Maven创建web项目

    一.创建一个Maven项目 1.Eclipse中用Maven创建项目 上图中点击next 2.继续next 3.选maven-archetype-webapp后,next 4.填写相应的信息,Pack ...

  10. POJ 1066 Treasure Hunt (线段相交)

    题意:给你一个100*100的正方形,再给你n条线(墙),保证线段一定在正方形内且端点在正方形边界(外墙),最后给你一个正方形内的点(保证不再墙上) 告诉你墙之间(包括外墙)围成了一些小房间,在小房间 ...