Erlang那些事儿第3回之我是函数(fun),万物之源MFA
Erlang代码到处都是模式匹配,这把屠龙刀可是Erlang的看家本领、独家绝学,之前在《Erlang那些事儿第1回之我是变量,一次赋值永不改变》文章提到过,Erlang一切皆是模式匹配。从变量的赋值、函数的形参传递、重载函数的应用,到处都有模式匹配的影子,刚开始写代码会感觉不习惯,但是当你用习惯之后,会觉得这个武林秘籍是多么的好用。但是本回书重点讲函数,毕竟以后写代码都会应用到函数fun,早点讲方便后面的使用。
Erlang语言中的函数很强大,同一个逻辑可以用多种写法,因为一个函数有形参,也有函数内部实现,这2个地方只要合理地应用模式匹配,那么可以发挥出非常大的作用。本回书会使用模式匹配、关卡、递归、apply、参数传递在函数中的用法。
知识点1:函数重载
特点1:形参数量不同;
特点2:函数之间用点号(.)分隔。
C++一个很重要的特性就是函数重载,这个特性的必要条件是函数的形参数量必须不一样。在Erlang代码中,这个规则同样适用,一个同名函数可以有多个不同参数数量的版本,导出列表也要相应地体现出来,来买个水果试试:
创建文件fruit_price01.erl,代码如下:
- 1 -module(fruit_price01).
- 2 -author("snowcicada").
- 3
- 4 %% API
- 5 -export([fruit_price/0, fruit_price/1]).
- 6
- 7 %% 买1个水果的价格
- 8 fruit_price() ->
- 9 fruit_price(1).
- 10
- 11 %% 买多个水果的价格
- 12 fruit_price(Count) ->
- 13 Count * 10.
上述代码存在2个fruit_price函数,一个是0参,一个是1参,如果有需要给其他模块使用的情况下,那么就要添加到export导出列表,来运行试一下:
- Eshell V11.1.3 (abort with ^G)
- 1> c(fruit_price01).
- {ok,fruit_price01}
- 2> fruit_price01:fruit_price().
- 10
- 3> fruit_price01:fruit_price(10).
- 100
在Erlang终端执行函数,不管是0参还是1参都能正常工作。
知识点2:函数形参的模式匹配
特点1:形参数量相同;
特点2:函数之间用分号;分隔;
特点3:从上往下匹配。
函数的每个形参都可以用一个表达式进行匹配,当传入的参数匹配上提前写好的表达式,那么就进入这个函数执行;如果不匹配的话,那么要么报错,要么会进入一个能够匹配的函数分支。
假设买1个水果没打折,买2个打8折,买3个以上打5折。创建文件fruit_price01.erl,代码如下:
- 1 -module(fruit_price02).
- 2 -author("snowcicada").
- 3
- 4 %% API
- 5 -export([fruit_price/1, discount/2]).
- 6
- 7 fruit_price(Count) ->
- 8 io:format("~p~n", [discount(Count, 10)]).
- 9
- 10 %% 函数形参进行模式匹配
- 11 %% 买1个没打折,买2个打8折,买3个以上打5折
- 12 discount(1 = Count, Price) ->
- 13 Count * Price;
- 14 discount(2 = Count, Price) ->
- 15 Count * Price * 0.8;
- 16 discount(Count, Price) ->
- 17 Count * Price * 0.5.
在Erlang语言中,等于号(=)并不是赋值,而是进行了一次模式匹配。所以第12行里面写的1 = Count是在匹配,匹配Count是否等于1,如果匹配成功,那么就会执行第13行的代码。第14行同理。
有趣的是第16行,只写了Count表示对任何数据都可以匹配成功,既然第12行、14行已经匹配了1和2,那么当Count等于3或3以上的时候就会执行第17行的代码。
知识点3:case...of...end表达式的模式匹配
特点1:不同的匹配表达式末尾用分号(;)分隔,最后一个匹配表达式不需要加分号(;);
特点2:下划线或者一个普通变量可以匹配任何情况;
特点3:从上往下匹配。
写代码总不能为了处理不同的情况而每次都写多个函数匹配,这样写起来不一定方便,所以Erlang还提供了case...of...end表达式。接下来使用case表达式来重写上面的discount函数。
新增函数discount_case,代码如下:
- 1 %% 参数模式匹配
- 2 %% 买1个没打折,买2个打8折,买3个以上打5折
- 3 discount_case(Count, Price) ->
- 4 case Count of
- 5 1 ->
- 6 Count * Price;
- 7 2 ->
- 8 Count * Price * 0.8;
- 9 _ -> %% 下划线也可以替换成一个变量,比如N,Cnt,都可以,只要是变量就行
- 10 Count * Price * 0.5
- 11 end.
case...of中间写的是表达式,of后面可以写入不一样的匹配表达式,匹配成功就会执行箭头后面的语句。
知识点4:函数关卡
函数外部可以对形参添加一些条件,指定不同的条件执行不同的函数,这里称为关卡。
新增函数discount_guard,代码如下:
- 1 %% 函数形参关卡判断
- 2 %% 买1个没打折,买2个打8折,买3个以上打5折
- 3 discount_guard(Count, Price) when Count =:= 1 ->
- 4 Count * Price;
- 5 discount_guard(Count, Price) when Count =:= 2 ->
- 6 Count * Price * 0.8;
- 7 discount_guard(Count, Price) ->
- 8 Count * Price * 0.5.
在箭头(->)前面,使用when关键字对形参进行判断,第3行显示,当Count等于1的时候,会执行这个函数。
知识点5:if关卡
同样还有更方便的关卡方式,就是使用if...end表达式。
新增函数discount_if,代码如下:
- 1 %% 参数关卡判断
- 2 %% 买1个没打折,买2个打8折,买3个以上打5折
- 3 discount_if(Count, Price) ->
- 4 if
- 5 Count =:= 1 ->
- 6 Count * Price;
- 7 Count =:= 2 ->
- 8 Count * Price * 0.8;
- 9 true ->
- 10 Count * Price * 0.5
- 11 end.
case和if的差别在于表达式写的是不是模式匹配,if中间那些表达式是用来判断是否相等,这种是很明确的相等比较。但是case中间的表达式放的是匹配表达式,而且case...of中间可以写复杂的表达式。
以下列出的关卡判断函数和关卡内置函数,可用于if关卡或者函数外的when关卡。
关卡判断函数:
关卡内置函数:
介绍以上4种版本的discount,我们调整下fruit_price函数,
- 1 fruit_price(Count) ->
- 2 io:format("~p~n", [discount(Count, 10)]),
- 3 io:format("~p~n", [discount_case(Count, 10)]),
- 4 io:format("~p~n", [discount_guard(Count, 10)]),
- 5 io:format("~p~n", [discount_if(Count, 10)]).
运行结果:
- Erlang/OTP 23 [erts-11.1.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe] [dtrace]
- Eshell V11.1.3 (abort with ^G)
- 1> c(fruit_price02).
- {ok,fruit_price01}
- 2> fruit_price02:fruit_price(10).
- 50.0
- 50.0
- 50.0
- 50.0
- ok
4个版本的discount运行结果都一样。
知识点6:函数作为参数传递
这个特性很常见,很多语言都可以把函数作为参数进行传递,只是语法有些小差异罢了。不啰嗦,写个例子吧,建个fruit_price03.erl文件,代码如下:
- 1 -module(fruit_price03).
- 2 -author("snowcicada").
- 3
- 4 %% API
- 5 -export([fruit_price/1, discount/2, get_discount_func/0]).
- 6
- 7 fruit_price(Count) ->
- 8 Discount = get_discount_func(),
- 9 io:format("~p~n", [Discount(Count, 10)]).
- 10
- 11 get_discount_func() ->
- 12 fun discount/2.
- 13
- 14 %% 函数形参进行模式匹配
- 15 %% 买1个没打折,买2个打8折,买3个以上打5折
- 16 discount(1 = Count, Price) ->
- 17 Count * Price;
- 18 discount(2 = Count, Price) ->
- 19 Count * Price * 0.8;
- 20 discount(Count, Price) ->
- 21 Count * Price * 0.5.
discount函数有2个形参,所以Erlang要返回一个函数,就如你所见的第12行,fun discount/2。
知识点7:递归函数
Erlang函数在处理模式匹配或者关卡的时候,可以有多个分支,就如同知识点2和知识点4的形式。通过这个方式,可以灵活的写出递归函数,对一些临界情况的处理,这里写个简单的例子就好,以后讲到列表的时候会使用到,用得很灵活有趣。
创建div_three.erl文件,代码如下:
- 1 -module(div_three).
- 2 -author("snowcicada").
- 3
- 4 %% API
- 5 -export([print/1]).
- 6
- 7 print(N) when N =:= 0 ->
- 8 io:format("~n");
- 9 print(N) when N rem 3 =:= 0 ->
- 10 io:format("~p ", [N]),
- 11 print(N - 1);
- 12 print(N) ->
- 13 print(N - 1).
当N等于0的时候,会运行第7行,函数输出换行立马结束;当N对3取余等于0的时候,执行第9行,可以被3整除的数字将会打印出来,然后继续调用print(N-1),这里就是递归调用。
当执行过了第7、第9行的关卡,剩下的都会执行第12行,这里什么都没处理,直接递归调用print即可。
执行结果:
- Erlang/OTP 23 [erts-11.1.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe] [dtrace]
- Eshell V11.1.3 (abort with ^G)
- 1> c(div_three).
- {ok,div_three}
- 2> div_three:print(100).
- 99 96 93 90 87 84 81 78 75 72 69 66 63 60 57 54 51 48 45 42 39 36 33 30 27 24 21 18 15 12 9 6 3
- ok
知识点8:使用apply调用函数
MFA是Module、Function、Arguments的缩写,指模块调用函数,传入形参,格式如:M:F(A),也可以这样:apply(M, F, A)。在Erlang自带的标准库中,MFA的调用方式很常见,也是Erlang实现热更新屡试不爽的步骤之一。其中的A很容易出现低级错误,大部分模块的参数支持传入列表,所以通常的调用方式如:M:F([A1, A2, A3])。
Erlang提供了apply函数,可通过指定模块名、函数名和参数进行调用,这里贴下apply的实现源码:
- 1 %% Shadowed by erl_bif_types: erlang:apply/2
- 2 -spec apply(Fun, Args) -> term() when
- 3 Fun :: function(),
- 4 Args :: [term()].
- 5 apply(Fun, Args) ->
- 6 erlang:apply(Fun, Args).
- 7
- 8 %% Shadowed by erl_bif_types: erlang:apply/3
- 9 -spec apply(Module, Function, Args) -> term() when
- 10 Module :: module(),
- 11 Function :: atom(),
- 12 Args :: [term()].
- 13 apply(Mod, Name, Args) ->
- 14 erlang:apply(Mod, Name, Args).
apply分别有2个形参和3个形参,2个形参的版本是apply(F,A),不用传入模块名,3个形参的版本是apply(M,F,A),需要指定模块名。
打开Erlang终端做个试验就行,使用io:format来测试打印信息:
- Erlang/OTP 23 [erts-11.1.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe] [dtrace]
- Eshell V11.1.3 (abort with ^G)
- 1> io:format("Name:~page:~p~n",["Lucy", 16]).
- Name:"Lucy"age:16
- ok
- 2> apply(io, format, ["Name:~p age:~p~n", [lucy, 16]]).
- Name:lucy age:16
- ok
- 3> erlang:apply(io, format, ["Name:~p age:~p~n", [lucy, 16]]). %%也可以指定erlang模块,这样写的好处是会有智能提示
- Name:lucy age:16
- ok
函数的应用大概就这些了,虽然简单,但是这些都是日常很经典的技巧。
本回使用的代码已上传Github:https://github.com/snowcicada/erlang-story/tree/main/story003
下一回将介绍原子(Atom)的使用,且听下回分解。
Erlang那些事儿第3回之我是函数(fun),万物之源MFA的更多相关文章
- Erlang那些事儿第2回之我是模块(module),一文件一模块
前几篇文章会写得比较基础,但是既然要写一系列的文章,还是得从基础开始写.我刚学Erlang碰到最大的问题是,想网上搜索下语法,结果却是寥寥无几,而且介绍得不是很系统,对我了解一些细节是有影响的,正好我 ...
- Erlang那些事儿第1回之我是变量,一次赋值永不改变
第1回先从不变的变量说开来,学过其他编程语言的人都知道,变量之所以叫变量,是因为它会经常变,被修改.假设原本X = 10,后来再执行X = 24,那么X就从10变成了24,这对于程序新手和老鸟来说, ...
- 安卓程序中手机后退键与标题栏后退键是不同的,前者回出发onBackPressed()函数,后者需要重重写temclick函数
安卓程序中手机后退键与标题栏后退键是不同的,前者回出发onBackPressed()函数,后者需要重重写temclick函数
- Erlang那些事儿之正儿八经的前言
说在前面,为啥要码这些,并不是因为喜欢它,恰恰相反,我非常讨厌Erlang(真香警告)这位二郎神(Erlang的谐音),讨厌它的语法,讨厌它不变的变量,讨厌它的一切. 曾经的我,一听到这个语言,我就打 ...
- C语言:判断字符串是否为回文,-函数fun将单向链表结点数据域为偶数的值累加起来。-用函数指针指向要调用的函数,并进行调用。
//函数fun功能:用函数指针指向要调用的函数,并进行调用. #include <stdio.h> double f1(double x) { return x*x; } double f ...
- 【C语言】14-返回指针的函数与指向函数的指针
前言 前面我们花了接近3个章节学习指针,应该都感受到指针的强大了吧.指针可以根据地址直接操作内存中的数据,使用得当的话,不仅能使代码量变少,还能优化内存管理.提升程序性能.关于指针的内容还非常多,比如 ...
- c++ 回调类成员函数实现
实现类成员函数的回调,并非静态函数:区分之 #ifndef __CALLBACK_PROXY_H_ #define __CALLBACK_PROXY_H_ template <typename ...
- 不是语言之争--Go vs Erlang
因为 云巴 系统对高并发.低延迟的需求,我们对各个语言.平台做了很多的调研比较工作.这自然就包括致力于开发高并发应用的 Go 和 Erlang. 并发 Go 对高并发的支持通过 goroutine 实 ...
- Go vs Erlang - 转
From http://zhang.hu/go-vs-erlang/ Go vs Erlang 因为 云巴 系统对高并发.低延迟的需求,我们对各个语言.平台做了很多的调研比较工作.这自然就包括致力于开 ...
随机推荐
- 因为一个Docker问题,我顺手整理从安装到常用命令操作手册
今天,自己写了一部分业务代码,是常规代码的另外一种方式,不能在公司的服务器上测试,就自己在PC端搭建了一套和公司集群一样的模板,因为公司的业务模块的测试有单独的服务器(这一块还是我很稀罕的),但是,第 ...
- 学习工具--Git
前言 主要内容来源于廖雪峰网站,内容通俗易懂,有些地方用了Gif来演示,实用性超强.至于git的强大,就不强调很多了,熟练掌握它最好的还是在实际工程中,先做一个简单的总结吧. git简介 Git是目前 ...
- java并发编程实战《二十一》无锁工具类
不安全的累加代码,如下 1 public class Test { 2 long count = 0; 3 void add10K() { 4 int idx = 0; 5 while(idx++ & ...
- java并发编程实战《二》java内存模型
Java解决可见性和有序性问题:Java内存模型 什么是 Java 内存模型? Java 内存模型是个很复杂的规范,可以从不同的视角来解读,站在我们这些程序员的视角,本质上可以理解为, Java 内存 ...
- 老猿学5G:融合计费场景的离线计费会话的Nchf_OfflineOnlyCharging_Create创建操作
☞ ░ 前往老猿Python博文目录 ░ 一.Nchf_OfflineOnlyCharging_Create消息交互流程 Nchf_OfflineOnlyCharging_Create服务化操作请求是 ...
- PyQt+moviepy音视频剪辑实战1:多个音视频合成顺序播放或同屏播放的视频文件实现详解
专栏:Python基础教程目录 专栏:使用PyQt开发图形界面Python应用 专栏:PyQt+moviepy音视频剪辑实战 专栏:PyQt入门学习 老猿Python博文目录 老猿学5G博文目录 一. ...
- upload 注意php远程安全模式屏蔽函数
进来:上传一个一句话php,果然不行:改成jpg后缀,上传成功:接着写一个.htaccess文件去把.jpg解析成.php,如下: AddType application/x-httpd-php .j ...
- SELECT 1,2,3...的含义及其在SQL注入中的用法
首先,select 之后可以接一串数字:1,2,3-只是一个例子,这串数字并不一定要按从小到大排列,也不一定从1开始,这串数字的值和顺序是任意的,甚至可以是重复的,如:11,465,7461,35 或 ...
- instanceof constructor Object.prototype.tostring.call ( [] )区别 数组和 对象的3中方法
- 算法数据结构——数的深搜和广搜(dfs和bfs)
leetcode104 二叉树的最大深度 https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/ 深度搜索分两种:递归(使用栈) ...