Erlang Introduction
- Erlang 官方文檔
- 高可用微服務(wù)
- 為啥 Erlang 沒有像 Go蓬戚、Scala 語言那樣崛起?
- 一位 Erlang 程序員的自白
- Learn you some erlang
- Sublime Erlang Plugin
- 游戲服務(wù)器難在哪笤昨?
- 《面對(duì)軟件錯(cuò)誤構(gòu)建可靠的分布式系統(tǒng)》
- Making reliabledistributed systemsin the presence ofsodware errors
- Erlang Movie
- Elixir Introduction
Erlang 最初是愛立信為開發(fā)電信相關(guān)產(chǎn)品而產(chǎn)生,即 OTP - Open Telecom Platform 縮寫,Erlang 開源前這個(gè)名字多少還有點(diǎn)品牌效應(yīng)印衔。
無論 Erlang 還是 OTP 都早已不再局限于電信應(yīng)用:更貼切的名字應(yīng)該是“并發(fā)系統(tǒng)平臺(tái)”。
Erlang 是一種面向并發(fā) Concurrency Oriented姥敛,面向消息 Message Oriented 的函數(shù)式 Functional 編程語言奸焙。
Erlang 應(yīng)用場景
- 分布式產(chǎn)品,網(wǎng)絡(luò)服務(wù)器彤敛,客戶端与帆,等各種應(yīng)用環(huán)境。
- Erlang 也可以作為一種快速開發(fā)語言墨榄,進(jìn)行原型開發(fā)玄糟。
- 應(yīng)用需要處理大量并發(fā)活動(dòng)。
- 需要良好的軟件或硬件 fault-tolerant 容錯(cuò)能力渠概。
- 軟件產(chǎn)品需要在多服務(wù)器中具有良好的伸縮能力茶凳,而不必大改動(dòng)。
- 容易實(shí)現(xiàn)不中斷服務(wù)進(jìn)行升級(jí)過程播揪,如游戲服務(wù)器贮喧。
- 軟件需要在嚴(yán)格的時(shí)間片響應(yīng)用戶,如游戲服務(wù)器猪狈。
我學(xué)了兩天兩夜的二郎箱沦,學(xué)它備用,游戲服務(wù)端開發(fā)非常有用雇庙!如果低延遲對(duì)你的應(yīng)用來說很重要谓形,那么平心而論灶伊,不選 Erlang 反而顯得很奇怪了。
游戲服務(wù)器是后端寒跳,做后端的聘萨,每天耳濡目染橫向擴(kuò)展,自動(dòng)伸縮等炫酷的特性童太,要說放在以前米辐,這些特性還是巨頭的”專利”,我們想要自己實(shí)現(xiàn)這些東西挑戰(zhàn)性是比較大的书释,但近幾年有了容器生態(tài)如 k8s 的加持翘贮,只要你實(shí)現(xiàn)了一個(gè)無狀態(tài)應(yīng)用,你幾乎馬上就可以得到一個(gè)可伸縮的集群爆惧,享受無狀態(tài)本身帶來的各種好處狸页,機(jī)器掛了自動(dòng)重啟,性能不夠了就自動(dòng)擴(kuò)展等等扯再。而作為一名游戲服務(wù)器開發(fā)者芍耘,自然也想充分享受容器時(shí)代的紅利。
面向并發(fā)說明 Erlang 支持大規(guī)模的并發(fā)應(yīng)用叔收,我們可以在應(yīng)用中處理成千上萬的并發(fā)齿穗,而不相互影響傲隶。面向消息饺律,其實(shí)是為并發(fā)服務(wù)!我們應(yīng)該都熟悉多線程跺株,熟悉加鎖解鎖操作复濒,熟悉可能出現(xiàn)的資源競爭與死鎖。在 Erlang 的世界里乒省,我們可以將輕輕的抹去這些令人苦惱的詞匯巧颈。Erlang 的世界,每個(gè)處理都是獨(dú)立的個(gè)體袖扛,他們之間的交互僅僅靠消息砸泛!因此不會(huì)有死鎖,不會(huì)有那種痛苦的編程經(jīng)歷蛆封。
Erlang 中一個(gè)非常重要的名詞 Process
唇礁,也就是我們前面提到的個(gè)體。它不是我們操作系統(tǒng)中的進(jìn)程惨篱,也不是線程盏筐。它是 Erlang 提供給我們的超級(jí)輕量的進(jìn)程。為了適應(yīng)大規(guī)模并發(fā)的特性砸讳,Process 需要能夠快速創(chuàng)建琢融,快速銷毀界牡。Process 之間通信的唯一方法就是消息,我們只要知道一個(gè) Process 的名字即 pid漾抬,就可以向其發(fā)送消息宿亡。Process 也可以在任何時(shí)候,接收消息纳令。我們這樣做只有一個(gè)目的:讓我們的系統(tǒng)更加簡單她混,用一種樸素的做法,實(shí)現(xiàn)一個(gè)高效的語言泊碑。
Erlang 是種函數(shù)式編程語言坤按,對(duì)此我沒有很深刻的理解,最明顯的特征就是馒过,Erlang 中到處都是函數(shù)臭脓,函數(shù)構(gòu)成了我們的產(chǎn)品的主體,把這些函數(shù)放到一個(gè)個(gè)的 Process 中去腹忽,讓他們運(yùn)行起來来累,那么就組成了我們朝氣蓬勃的產(chǎn)品。
Erlang 支持對(duì)數(shù)據(jù)的位操作窘奏,擁有豐富的數(shù)據(jù)持久化機(jī)制嘹锁。
同時(shí)需要說明的是 Erlang 內(nèi)建垃圾回收機(jī)制 GC。
作為 Erlang 創(chuàng)始人着裹,瑞典 Joe Armstrong 在軟件工程領(lǐng)域貢獻(xiàn)是巨大的领猾,不必說 Erlang 與 OTP, 光他的論文《面對(duì)軟件錯(cuò)誤構(gòu)建可靠的分布式系統(tǒng)》就足以載入史冊骇扇,領(lǐng)先現(xiàn)在幾十年摔竿,提出了OOP 等思想本質(zhì)上不是并發(fā)的正確處理方法。
2019年4月20日少孝,Erlang 語言設(shè)計(jì)者 Joe Armstrong 去世继低,享年 68 歲。
1986 年稍走,Joe Armstrong 和 Robert Virding袁翁、Mike Williams 在電信公司愛立信共同創(chuàng)造了應(yīng)對(duì)大規(guī)模并發(fā)場景的編程語言 Erlang,這一語言起初是愛立信的私有語言婿脸,后于 1998 年開源粱胜。
Erlang 是一門相對(duì)小眾的編程語言,這一點(diǎn)與 Lisp 很像 —— 小眾但影響很大盖淡。Joe Armstrong 曾用一句話概括過 Erlang 的優(yōu)點(diǎn):一次編寫年柠,永遠(yuǎn)運(yùn)行。
Joe Armstrong 在論文中是這樣認(rèn)為的:幾乎所有傳統(tǒng)的編程語言對(duì)真正的并發(fā)都缺乏有力的支持——本質(zhì)上是順序化的,而語言的并發(fā)性都僅僅由底層操作系統(tǒng)而不是語言提供冗恨。
而用對(duì)并發(fā)提供良好支持的語言答憔,也就是作者說的面向并發(fā)的語言
COL - Concurrency Oriented Language 來邊寫程序,則是相當(dāng)容易的:
- 從真實(shí)世界的活動(dòng)中識(shí)別出真正的并發(fā)活動(dòng)
- 識(shí)別出并發(fā)活動(dòng)之間的所有消息通道
- 寫下能夠在不同消息通道中流通的所有消息
其次掀抹,通過定下的九條原則性思想設(shè)計(jì)虐拓,寫出來天然支持分布式系統(tǒng)的 Erlang 以及 OTP 框架,真的做到了他說的實(shí)現(xiàn)面向并發(fā)的語言傲武。
- 一切皆進(jìn)程
- 進(jìn)程強(qiáng)隔離
- 進(jìn)程的生成與銷毀都是輕量的操作
- 消息傳遞是進(jìn)程交互的唯一方式
- 每個(gè)進(jìn)程有唯一的名字
- 你若知道進(jìn)程的名字蓉驹,就可以向他發(fā)消息
- 進(jìn)程之間不共享資源
- 錯(cuò)誤處理非本地化
- 進(jìn)程要么正常跑著,要么馬上掛掉
就以上九條的觀念揪利,設(shè)計(jì)出的 Erlang 語言态兴,成就了可靠性達(dá)到 99.9999999% 的目前世界上最復(fù)雜的 ATM 交換機(jī)。
其三疟位,let it crash 思想的提出與實(shí)現(xiàn)瞻润。
程序不可能處理一切錯(cuò)誤,因此程序員只要力所能及的處理顯然易見的錯(cuò)誤就好了甜刻,而那些隱藏著的绍撞,非直覺性的錯(cuò)誤,就讓他崩掉吧——本來就很有可能是極少見的錯(cuò)誤得院,經(jīng)常出現(xiàn)的傻铣?就需要程序員人工處理了,這是緊急情況祥绞,就算 try catch 所有錯(cuò)誤也無法避免非洲,因?yàn)橄到y(tǒng)已經(jīng)陷入崩潰邊緣了,茍延殘喘下去只是自欺欺人就谜。并且怪蔑,不恰當(dāng)?shù)厥褂?try catch 還會(huì)埋下隱患,讓系統(tǒng)帶病運(yùn)轉(zhuǎn)丧荐。
其四,一切進(jìn)程都是輕量級(jí)的喧枷,都可以被監(jiān)控 monitor虹统,有 Supervisor 專門做監(jiān)控。你可以方便的用一個(gè) supervisor 進(jìn)程去管理子進(jìn)程隧甚,supervisor 會(huì)根據(jù)你設(shè)定的策略车荔,來處理意外掛掉的子進(jìn)程。這種情況的問題的是戚扳,錯(cuò)誤處理稍微做不好就會(huì)掛忧便,策略有:
- one_for_one:只重啟掛掉的子進(jìn)程
- one_for_all:有一個(gè)子進(jìn)程掛了,重啟所有子進(jìn)程
- rest_for_one:在該掛掉的子進(jìn)程 創(chuàng)建時(shí)間之后創(chuàng)建的子進(jìn)程都會(huì)重啟帽借。
Erlang 語言特性
- 簡單小巧
Erlang 簡單小巧只有 6 種基本的數(shù)據(jù)類型珠增,另外提供幾種復(fù)合結(jié)構(gòu)超歌,這就是 Erlang 的所有數(shù)據(jù)類型。
Atom
bitstring
-
Number
(float
,integer
) List
Maps
Tuple
Reference
Fun
Port
Pid
String
Record
Boolean
在 Erlang 中表示任何類型的數(shù)據(jù)都叫做 Terms
蒂教,它是源代碼中的基本數(shù)據(jù)類型巍举。而常見的 string
在 Erlang 中是以位串 bitstring
或 List
表達(dá)的。沒有 Boolean 類型凝垛,使用 atoms 原子類型的 true & false 替代懊悯。
- 模式匹配
在 Erlang 的函數(shù)中,= 號(hào)不是賦值梦皮,而是模式匹配炭分,某些語法中,如 C# 8.0 也可以使用 Pattern 匹配剑肯,這是一個(gè)非常好的特性欠窒,我們可以讓代碼自己去決定如何執(zhí)行。
比如退子,我們定義一個(gè)函數(shù)岖妄,其告訴我們某種水果的價(jià)格:
price(apple) -> 2.0;
price(banana) -> 1.2.
我們隨后調(diào)用 price(Fruit)
,會(huì)根據(jù) Fruit 變量的內(nèi)容返回具體的價(jià)格寂祥。這樣做的好處就是節(jié)省了我們的代碼量荐虐,我們不用 if...else… 或者 switch…case 的來伺候了。也便于代碼的擴(kuò)展:加一個(gè)新的水果品種丸凭,我們只需要加一行就可以了福扬。
學(xué)習(xí) Erlang 一個(gè)非常重要的內(nèi)容就是模式匹配
,但是請不要混淆惜犀,這個(gè)匹配和正則表達(dá)式?jīng)]有任何干系铛碑。
- 變量單次賦值
一個(gè)匪夷所思的特性,變量竟然只能單次賦值
虽界!是的 Erlang 中變量一旦綁定某個(gè)數(shù)值以后汽烦,就不能再次綁定,這樣做的好處是便于調(diào)試出錯(cuò)莉御,更深層次的原因是 Erlang 為并發(fā)設(shè)計(jì)撇吞,如果變量可以修改,那么就涉及到資源的加鎖解鎖等問題礁叔,當(dāng)發(fā)生錯(cuò)誤時(shí)牍颈,某個(gè)變量是什么就永遠(yuǎn)是什么,不用順藤摸瓜的查找誰修改過它琅关,省了好多事情煮岁。唯一的麻煩就是需要一個(gè)信的變量時(shí),你必須再為它想一個(gè)名字。
- Erlang 中提供豐富的 libs
-
stdlib
中包含大量的數(shù)據(jù)結(jié)構(gòu)如 lists画机,array冶伞,dict,gb_sets色罚,gb_trees碰缔,ets,dets 等 -
mnesia
提供一個(gè)分布式的數(shù)據(jù)庫系統(tǒng) -
inets
提供 ftp client戳护,http client/server金抡,tftp client/server -
crypto
提供加密解密相關(guān)函數(shù),基于 openssl 相關(guān)實(shí)現(xiàn) -
ssl
實(shí)現(xiàn)加密socket通信腌且,基于openssl實(shí)現(xiàn) -
ssh
實(shí)現(xiàn)ssh協(xié)議 -
xmerl
實(shí)現(xiàn)XML相關(guān)解析 -
snmp
實(shí)現(xiàn)SNMP協(xié)議(Simple Network Management Protocol) -
observer
用來分析與追蹤分布式應(yīng)用 -
odbc
使 Erlang 可以連接基于SQL的數(shù)據(jù)庫 -
orber
實(shí)現(xiàn) CORBA 對(duì)象請求代理服務(wù) -
os_mon
提供對(duì)操作系統(tǒng)的監(jiān)控功能 -
dialyzer
提供一個(gè)靜態(tài)的代碼或程序分析工具 -
edoc
依據(jù)源文件生成文檔 -
gs
可以為我們提供某些 GUI 的功能(基于Tcl/Tk)
還有很多朋友提供了一些開源的lib梗肝,比如eunit,用來進(jìn)行單元測試铺董。
- 靈活多樣的錯(cuò)誤處理
Erlang 最初為電信產(chǎn)品的開發(fā)巫击,這樣的目的,決定了其對(duì)錯(cuò)誤處理的嚴(yán)格要求精续。Erlang 中提供一般語言所提供的 exception坝锰,catch,try…catch 等語法重付,同時(shí) Erlang 支持 Link
和 Monitor
兩種機(jī)制顷级,我們可以將 Process 連接起來,讓他們組成一個(gè)整體确垫,某個(gè) Process 出錯(cuò)弓颈,或退出時(shí),其他 Process 都具有得知其推出的能力删掀。而 Monitor 顧名思義翔冀,可以用來監(jiān)控某個(gè) Process,判斷其是否退出或出錯(cuò)披泪。所有的這些 Erlang 都提供內(nèi)在支持纤子,我們快速的開發(fā)堅(jiān)固的產(chǎn)品,不再是奢望付呕。
- 代碼熱替換
你的產(chǎn)品想不間斷的更新么计福?Erlang 可以滿足你這個(gè)需求,Erlang 會(huì)在運(yùn)行時(shí)自動(dòng)將舊的模塊進(jìn)行替換徽职。一切都靜悄悄。
- 天生的分布式
Erlang 天生適合分布式應(yīng)用開發(fā)佩厚,其很多的 BIF 內(nèi)建函數(shù)都具有分布式版本姆钉,我們可以通過 BIF 在遠(yuǎn)程機(jī)器上創(chuàng)建 Process,可以向遠(yuǎn)程機(jī)器上的某個(gè) Process 發(fā)送消息。在分布式應(yīng)用的開發(fā)中潮瓶,我們可以像 C陶冷、C++,Java 等語言一樣毯辅,通過 Socket 進(jìn)行通訊埂伦,也可以使用 Erlang 內(nèi)嵌的基于 Cookie 的分布式架構(gòu),進(jìn)行開發(fā)思恐。當(dāng)然也可以兩者混合沾谜。分布式開發(fā)更加方便,快速胀莹。Erlang 的 Process 的操作基跑,Error 的處理等都對(duì)支持分布式操作。
- 超強(qiáng)的并發(fā)性
由于采用其自身 Process描焰,而沒有采用操作系統(tǒng)的進(jìn)程和線程媳否,我們可以創(chuàng)建大規(guī)模的并發(fā)處理,同時(shí)還簡化了我們的編程復(fù)雜度荆秦。我們可以通過幾十行代碼實(shí)現(xiàn)一個(gè)并發(fā)的TCP服務(wù)器篱竭,這在其他語言中都想都不敢想!
- 多核支持
Erlang讓您的應(yīng)用支持多個(gè)處理器步绸,您不需要為不同的硬件系統(tǒng)做不同的開發(fā)掺逼。采用Erlang將最大限度的發(fā)揮你的機(jī)器性能。
- 跨平臺(tái)
如同JAVA一樣靡努,Erlang 支持跨平臺(tái)(其目前支持linux坪圾,mac,windows等19種平臺(tái))惑朦,不用為代碼的移植而頭疼兽泄。
我們僅僅需要了解平臺(tái)的一些特性,對(duì)運(yùn)行時(shí)進(jìn)行優(yōu)化漾月。
- 開源
開源是我非常喜歡的一個(gè)詞匯病梢,開源意味這更加強(qiáng)壯,更加公開梁肿,更加的追求平等蜓陌。開源會(huì)讓 Erlang 更好。
Erlang 與外界的交互
Erlang 可以與其他的語言進(jìn)行交互吩蔑,如 C钮热、C++,Java烛芬。當(dāng)然也有熱心的朋友提供了與其他語言的交互隧期,如果需要你也可以根據(jù) Erlang 的數(shù)據(jù)格式飒责,提供一個(gè)庫,讓 Erang 與您心愛的語言交互仆潮。
Erlang 支持分布式開發(fā)宏蛉,您可以創(chuàng)建一個(gè) C Node,其如同一個(gè) Erlang 節(jié)點(diǎn)性置,前提是你遵照 Erlang 的規(guī)范拾并。
當(dāng)然最常用的交互還是再同一個(gè) Node 上,比如我們要調(diào)用某個(gè) lib鹏浅,調(diào)用一些系統(tǒng)提供的功能嗅义,這時(shí)候主要有兩種方式:Port 和嵌入式執(zhí)行。
Port 是 Erlang 最基本的與外界交互的方式篡石,進(jìn)行交互的雙方通過編碼芥喇,解碼,將信息以字節(jié)流的方式進(jìn)行傳遞。(具體這個(gè)通道的實(shí)現(xiàn)方式,根據(jù)操作系統(tǒng)的不同而不同停巷,比如 Unix 環(huán)境下,采用 PIP E實(shí)現(xiàn)武通,理論上任何支持對(duì)應(yīng) Port 通道實(shí)現(xiàn)的語言都可以與 Erlang 進(jìn)行交互)。Erlang 為了方便 C 和 JAVA 程序員珊搀,提供了 Erl_Interface 和 Jinterface冶忱。
采用 Port,您的代碼在 Erlang 的平臺(tái)之外運(yùn)行境析,其崩潰不會(huì)影響 Erlang囚枪。
嵌入式執(zhí)行,通過 Erlang 平臺(tái)加載劳淆,因此這是非常危險(xiǎn)的链沼,如果您的程序崩潰,沒有任何理由沛鸵,Erlang 也會(huì)崩潰括勺。
Erlang 批評(píng)者
曾經(jīng)使用過一段時(shí)間 Erlang,結(jié)論是:方便的地方真的方便曲掰,但麻煩的地方真的很麻煩疾捍。最終放棄 Erlang 并不是因?yàn)樯鐓^(qū),文檔栏妖,或者開源項(xiàng)目的多少乱豆,而是因?yàn)檎Z言本身。首先是狀態(tài)問題吊趾,比如要在 Erlang 中操作二維地圖咙鞍,很多人都選擇用C來實(shí)現(xiàn):Erlang 如何操作游戲中的二維地圖房官?
游戲引擎 Erlang 寫無狀態(tài)的代碼是非常的爽的趾徽,代碼就像一個(gè)個(gè)數(shù)學(xué)公式把程序給 “定義” 出來续滋,模式匹配有時(shí)也很高效。確實(shí)很適合電信系統(tǒng)這種請求與請求間隔離的孵奶,前后邏輯關(guān)系不大的“非狀態(tài)系統(tǒng)”疲酌,比如 HTTP,比如棋牌或者回合制游戲了袁。但兩個(gè)請求間如果邏輯交互很頻繁朗恳,比如動(dòng)作游戲,ARPG载绿,兩個(gè)角色間的交互頻繁了粥诫,牽扯裝太多了,用 Erlang就比較麻煩了崭庸,別人一個(gè)函數(shù)調(diào)用解決的問題怀浆,Erlang可能要幾個(gè)actor之間不停的消息中轉(zhuǎn)。
Erlang是一個(gè)專業(yè)化定制程度很高的語言(非狀態(tài)類電信系統(tǒng)怕享,請求隔離)执赡,所以不能因?yàn)?Erlang 在有的地方比其他語言開發(fā)效率高8倍(盡管似乎號(hào)稱),就覺得 Erlang在任何時(shí)候開發(fā)效率都很高函筋,比如你在 .BAT 文件里面可以這樣:
DEL d:\temp\*.jpg
換成 C++ 可能要寫7沙合,8行,大家就覺得 .BAT比 C++方便一樣跌帐。處理文件和目錄或許是首懈,但你說用BAT寫點(diǎn)除此之外別的東西,它就傻逼了谨敛,Erlang 也是一樣究履,方便的地方挺方便,別扭的地方別扭死你佣盒,關(guān)鍵還是 Scala 和 Go 的設(shè)計(jì)充滿了“妥協(xié)”挎袜,而 Erlang 里充滿了 “各種原則”。在適合的領(lǐng)域肥惭,這些原則能讓你很酸爽盯仪,而跳出那個(gè)圓圈,這些 “絕不妥協(xié)的原則” 會(huì)讓你花數(shù)倍的時(shí)間和精力去完成原本很直接的事情蜜葱。
- Erlang陡峭的學(xué)習(xí)曲線
大多數(shù)情況下全景,學(xué)一門新語言,大部分基本概念都可以靠其他語言的經(jīng)驗(yàn)快速理解牵囤,比如你如果學(xué)過 C爸黄,再學(xué) Java 不是什么難事滞伟。但是 erlang 正相反,你要先設(shè)法忘掉其他語言的一些概念炕贵,比如變量這個(gè)概念梆奈,在 erlang 中是不存在的。這些概念是如此地根深蒂固称开,讓我很難 think in erlang亩钟,以至于讀完一本 erlang 的教程,我仍然寫不出來斐波那契數(shù)列的程序.
- 這門語言沒有什么 killer app
每一種流行的語言都一定有用這種語言實(shí)現(xiàn)的鳖轰、應(yīng)用廣泛的系統(tǒng)清酥,以及由此衍生的龐大社區(qū)。社區(qū)中的布道者會(huì)把這個(gè)語言推向更多的應(yīng)用場景蕴侣。比如 php 的 wordpress焰轻、druple,python 的 web 框架 Django昆雀,用于機(jī)器學(xué)習(xí)的 sklearn辱志。但是對(duì)于 erlang,除了 rabbitmq忆肾,jabber荸频,似乎沒有太多 killer app。
Getting Started
- Erlang Reference Manual User's Guide
- Erlang 官方文檔
- Programming Erlang 第6章筆記 編譯和運(yùn)行
- Erlang 系統(tǒng)運(yùn)行
- Codes in Programming Erlang
Erlang 官方文檔提供以下內(nèi)容客冈,其中用戶手冊根據(jù)不同的內(nèi)容特點(diǎn)分成四個(gè)部分:
- Erlang/OTP Documentation
- Erldocs
- Erlang Reference Manual - User's Guide
- Efficiency Guide - User's Guide
- System Principles - User's Guide
- OTP Design Principles - User’s Guide
- OTP Versions Tree
還提供書籍 Erlang books:
- Programming Erlang: Software for a Concurrent World
- Learn You Some Erlang for Great Good!
- Erlang Programming
- Erlang and OTP in Action
- Introducing Erlang
- Designing for Scalability with Erlang/OTP
可以直接從 Erlang Reference Manual 開始從零學(xué)習(xí)旭从,或者跟隨 Erlang 實(shí)踐 Erlang and OTP in Action。
安裝 Erlang 后场仲,需要將 Erlang 的 bin 目錄加入環(huán)境變量 Path 之中和悦。
Erlang/OTP 文件類型:
Extension | File Type | Documented in |
---|---|---|
.erl | Module | Erlang Reference Manual |
.hrl | Include file | Erlang Reference Manual |
.rel | Release resource file | rel(4) manual page in SASL |
.app | Application resource file | app(4) manual page in Kernel |
.script | Boot script | script(4) manual page in SASL |
.boot | Binary boot script | - |
.config | Configuration file | config(4) manual page in Kernel |
.appup | Application upgrade file | appup(4) manual page in SASL |
relup | Release upgrade file | relup(4) manual page in SASL |
在 Sublime 上編寫程序,只需發(fā)配置以下編譯配置渠缕,將文件保存到 Packages\User\erlang.sublime-build
:
{
"env": {
"path":"c:\\Program Files\\erl10.4\\bin;%path%"
},
"working_dir": "$file_path",
"cmd": "csc.exe $file",
"file_regex":"^([^:]+):(?:([0-9]+):)?(?:([0-9]+):)? (.*)",
"selector": "source.erlang",
"encoding": "cp936",
"quiet": true,
"variants": [{
"name": "Run ...",
"shell_cmd": "erlc $file_name && erl -noshell -s $file_base_name start -s init stop"
}]
}
helloworld
Erlang 程序的運(yùn)行一般需要兩個(gè)步驟鸽素,即編譯和運(yùn)行。通過編譯生成與程序文件的主文件名相同而擴(kuò)展名為 .beam
亦鳞。要運(yùn)行 Erlang 程序馍忽,可以在 Erlang 的交互式命令行下或直接在命令行下編譯后運(yùn)行。
如下燕差,執(zhí)行 erl 命令或窗口版 werl 開始 HelloWorld遭笋,q()
其實(shí)只是 shell 上 init:stop()
的別名:
>erl
Eshell V10.4 (abort with ^G)
1> io:format("Hello WOrld!").
Hello WOrld!ok
2> q().
ok
3>
使用 Erlang shell 編譯運(yùn)行 .erl
程序
c(hello).
編寫一個(gè) hello.erl 程序,后面有三種方式運(yùn)行它:
-module(hello).
-export([fac/1]).
fac(0) -> 1;
fac(N) -> N * fac(N-1).
把這些存儲(chǔ)到文件 hello.erl 中徒探,文件名必須與模塊名相同瓦呼。
-
-module
表示定義一個(gè)模塊; -
-export
表示導(dǎo)出一個(gè)函數(shù)列表测暗,列表格式 [Fun/N1, Fun/N2 ...]央串,數(shù)字是參數(shù)個(gè)數(shù)磨澡,這里只導(dǎo)出了一個(gè) fac 函數(shù); -
-import(io,[fwrite/1]).
導(dǎo)入函數(shù)的格式類似導(dǎo)出质和,它需要指定導(dǎo)入的模塊稳摄; -
%
注解符號(hào),沒有注釋塊侦另; -
.
句點(diǎn)表示 Erlang 代碼的行的結(jié)束秩命,每條語句都需要句點(diǎn)結(jié)束。
使用 erl 編譯這個(gè)程序使用如下命令褒傅,并且運(yùn)行:
3> c(hello).
{ok,hello}
30> hello:fac(20).
2432902008176640000
4> hello:fac(40).
815915283247897734345611269596115894272000000000
32> _
確保工作目錄與程序所在目錄為同一個(gè)目錄,避免 erl 找不到文件袄友。然后執(zhí)行編譯 c(hello).
出現(xiàn) {ok殿托,hello}
說明編譯成功,可以執(zhí)行程序了剧蚣。
在命令行編譯和運(yùn)行支竹,erlc 命令提供了一個(gè)公共的途徑來運(yùn)行所有 Erlang 系統(tǒng)的編譯器,erlc 會(huì)根據(jù)于各輸入文件的擴(kuò)展名來調(diào)用合適的編譯器鸠按。
$ erlc hello.erl
$ erl -noshell -s hello fac -s init stop
Erlc 編譯一個(gè)或一個(gè)以上文件礼搁,文件必須包括它們的擴(kuò)展名,需要通過擴(kuò)展名來調(diào)用正確的編譯器目尖。例如 .erl 代表 Erlang 源代碼馒吴,而 .yrl 代表 Yecc 源代碼。
使用 erl 命令來調(diào)用模塊中的函數(shù)運(yùn)行程序瑟曲,設(shè)置參數(shù)如下:
-
-noshell
啟動(dòng) Erlang 而沒有交互式 shell饮戳,此時(shí)不會(huì)提示 Erlang 的啟動(dòng)信息 -
-s hello fac
運(yùn)行函數(shù) hello:fac() ,注意使用 -s Mod ... 選項(xiàng)時(shí)洞拨,相關(guān)的模塊 Mod 必須已經(jīng)編譯完成了扯罐。 -
-s init stop
當(dāng)我們調(diào)用 apply(hello,fac,[]) 結(jié)束時(shí),系統(tǒng)就會(huì)對(duì)函數(shù) init:stop() 求值烦衣。
使用 escript 可以直接運(yùn)行程序歹河,不需要先編譯。想要以 escript 方式運(yùn)行 hello花吟,需要?jiǎng)?chuàng)建如下文件秸歧,提供 main(_)
入口函數(shù):
#! /usr/bin/env escript
-module (coding).
-export ([start/0]).
main(_) ->
io:format("Hello world\n").
start() ->
io:format("Hello World! ~n").
% io:format("consulting .erlang in ~p~n",[element(2,file:get_cwd())]).
% c:cd("g:/programing/programingerlang").
% io:format("Now in:~p~n",[element(2,file:get_cwd())]).
然后執(zhí)行:
> escript hello
Hello world
在 Linux 中編寫 Shell 程序運(yùn)行 Erlang:
#!/bin/sh
#---
# Excerpted from "Programming Erlang",
# published by The Pragmatic Bookshelf.
# Copyrights apply to this code. It may not be used to create training material,
# courses, books, articles, and the like. Contact us if you are in doubt.
# We make no guarantees that this code is fit for any purpose.
# Visit http://www.pragmaticprogrammer.com/titles/jaerlang for more book information.
#---
erl -noshell -pa /home/joe/2009/book/JAERLANG/Book/code\
-s hello start -s init stop
使用格式輸出:
-module(helloworld).
-export([start/0]).
start() ->
X = 40.00,
Y = 50.00,
io:fwrite("~f~n",[X]),
io:fwrite("~e",[Y]).
Output
40.000000
5.00000e+1
輸出使用的格式字符串一般格式 ~F.P.PadModC.
:
-
~
波浪號(hào)表示格式定義; -
C
決定輸出數(shù)據(jù)類型示辈,這是必要的寥茫,其它如 F、P矾麻、Pad纱耻、Mod 部分都是可選的芭梯;-
~c
定義數(shù)字顯示為 ASCII 字符串格式: -
~s
定義字符串格式: -
~f
定義浮點(diǎn)數(shù)格式: -
~n
定義換行符號(hào); -
~e
科學(xué)計(jì)數(shù)法格式弄喘,默認(rèn)精度 6 位玖喘,至少 2 位;
-
-
Pad
定義填充符號(hào)蘑志,默認(rèn)是空格累奈,比如填充 # 號(hào),~..#C
急但; -
Mod
定義控制修飾序列澎媒,如貨幣是 t,: -
P
定義精度: -
F
定義字段寬度 Field width波桩,負(fù)值表示左對(duì)齊戒努,省略表示按數(shù)據(jù)要求長度輸出,如果指定寬度不足則用 * 填充: -
B
定義基數(shù)镐躲,2-36储玫,默認(rèn)是 10,比如二進(jìn)制顯示io:fwrite("~.16B~n", [31]).
-
X
類似B
但使用前綴萤皂,比如 16 進(jìn)制前顯示 0x撒穷,io:fwrite("~.16X~n", [-31,"0x"]).
Mod is the control sequence modifier. This is one or more characters that change the interpretation of Data. The current modifiers are t, for Unicode translation, and l, for stopping p and P from detecting printable characters.
If F, P, or Pad is a * character, the next argument in Data is used as the value. For example:
1> io:fwrite("~*.*.0f~n",[9, 5, 3.14159265]).
003.14159
ok
To use a literal * character as Pad, it must be passed as an argument:
2> io:fwrite("~*.*.*f~n",[9, 5, $*, 3.14159265]).
**3.14159
ok
在 erl 中使用常用 sheel 函數(shù):
- b() ? Prints the current variable bindings.
- f() ? Removes all current variable bindings.
- f(x) ? Removes the binding for a particular variable.
- h() ? Prints the history list of all the commands executed in the shell.
- history(N) ? 設(shè)置歷史記錄為 N 條,返回舊設(shè)置值裆熙,默認(rèn)值 20端礼。
- e(N) ? 重復(fù)執(zhí)行 N 號(hào)命令,如果 N 為負(fù)數(shù)則從最的位置回?cái)?shù)弛车,如 e(-1) 執(zhí)行上一條命令齐媒。
- q() - 退出
Erlang 數(shù)字前面可以用 # 來標(biāo)注其 Base,語法:Base#Value纷跛,默認(rèn)的 Base 是 10 進(jìn)制:
10> 2#101010. %% 2 進(jìn)制的 101010
42
11> 8#0677. %% 8 進(jìn)制的 0677
447
12> 16#AE. %% 16 進(jìn)制的 AE
174
Erlang 是函數(shù)式語言(雖然也支持副作用)喻括。這意味著 Erlang 里的變量 ‘ Immutable’ (不可變的).
Immutable variables 在設(shè)計(jì)上簡單,減少了并發(fā)過程中處理狀態(tài)改變帶來的復(fù)雜性贫奠。理解這一點(diǎn)很重要唬血。
Erlang 是動(dòng)態(tài)類型的語言,但它也是強(qiáng)類型的語言唤崭。動(dòng)態(tài)類型意味著你聲明變量時(shí)不需要指定類型拷恨,而強(qiáng)類型是說,erlang 不會(huì)偷偷做類型轉(zhuǎn)換:
1> 6 + "1".
** exception error: bad argument in an arithmetic expression
in operator +/2
called as 6 + "1"
Erlang 里變量的命名有約定谢肾,必須首字母大寫腕侄。因?yàn)槭鬃帜感懙模瑫?huì)被認(rèn)為是 atom (原子) 類型。
Erlang 里沒有賦值語句冕杠,= 號(hào)在 Erlang 里是 pattern matching 模式匹配微姊。
Operators 四類操作符
Arithmetic operators
Operator | Description | Example |
---|---|---|
+ | 兩數(shù)相加 | 1 + 2 = 3 |
? | 兩數(shù)相減 | 1 - 2 = -1 |
* | 兩數(shù)相乘 | 2 * 2 = 4 |
/ | 兩數(shù)相除 | 2 / 2 = 1 |
rem | 求余 | 3 rem 2 = 1 |
div | 整除 | 3 div 2 will give 1 |
Relational operators
Operator | Description | Example |
---|---|---|
== | 判斷是否相等 | 2 = 2 = true |
/= | 判斷是否不等 | 3 /= 2 = true |
< | 左側(cè)是否小于右側(cè) | 2 < 3 = true |
> | 左側(cè)是否大于右側(cè) | 3 > 2 = true |
=< | 左側(cè)是否小于或等于右側(cè) | 2 =<3 = true |
>= | 左側(cè)是否大于或等于右側(cè) | 3 >= 2 = true |
Logical operators
| or | 邏輯或運(yùn)算 | true or true = true |
| and | 邏輯與運(yùn)算 | True and false = false |
| not | 邏輯非運(yùn)算 | not false = true |
| xor | 邏輯異或 | True xor false = true |
Bitwise operators 比特位運(yùn)算符號(hào)有四個(gè),在邏輯運(yùn)算符前綴 b 就是對(duì)應(yīng)的位運(yùn)算分预。另外還有兩個(gè)移位操作:
- bsl (Bit Shift Left)
- bsr (Bit Shift Right)
注意兢交,以下數(shù)值是十六進(jìn)制,如下:
-module(helloworld).
-export([start/0]).
start() ->
io:fwrite("~w~n",[00111100 band 00001101]),
io:fwrite("~w~n",[00111100 bxor 00111100]),
io:fwrite("~w~n",[bnot 00111100]),
io:fwrite("~w~n",[00111100 bor 00111100]).
Output
76
0
-111101
111100
Escape Sequences
轉(zhuǎn)義符號(hào)笼痹,在字符串或單引號(hào)包括的 atoms 原子類型中使用:
轉(zhuǎn)義符號(hào) | 意義 |
---|---|
\b | Backspace |
\d | Delete |
\e | Escape |
\f | Form feed |
\n | Newline |
\r | Carriage return |
\s | Space |
\t | Tab |
\v | Vertical tab |
\XYZ, \YZ, \Z | 代表八制字符 XYZ, YZ or Z |
\xXY | 代表十六進(jìn)制字符 XY |
\x{X...} | 代表十六進(jìn)制字符配喳, X... 表示多個(gè)十六進(jìn)制字符 |
^a...^z, ^A...^Z | 控制字符 Control A to control Z |
' | Single quote |
" | Double quote |
\ | Backslash |
Decision Making 條件決策
If 語句的一般形式、多條件判斷和嵌入式凳干,如下面的程序所顯示晴裹,
if
condition1 ->
statement#1;
condition2 ->
statement#2;
conditionN ->
statement#N;
true ->
defaultstatement
end.
示例:
-module(helloworld).
-export([start/0]).
start() ->
A = 4,
B = 6,
if
A < B ->
if
A > 5 ->
io:fwrite("A is greater than 5");
true ->
io:fwrite("A is less than 5")
end;
true ->
io:fwrite("A is greater than B")
end.
Case Statements
case expression of
value1 -> statement#1;
value2 -> statement#2;
valueN -> statement#N
end.
示例:
-module(helloworld).
-export([start/0]).
start() ->
A = 5,
case A of
5 -> io:fwrite("The value of A is 5");
6 -> io:fwrite("The value of A is 6")
end.
Function 函數(shù)
函數(shù)定義的一般寫法,
FunctionName(Pattern1… PatternN) ->
Body;
示例:
-module(helloworld).
-export([add/2,add/3,start/0]).
add(X,Y) ->
Z = X+Y,
io:fwrite("~w~n",[Z]).
add(X,Y,Z) ->
A = X+Y+Z,
io:fwrite("~w~n",[A]).
start() ->
add(5,6),
add(5,6,6).
匿名函數(shù)纺座,沒有與任何名稱相關(guān)聯(lián)息拜,示例
-module(helloworld).
-export([start/0]).
start() ->
Fn = fun() ->
io:fwrite("Anonymous Function") end,
Fn().
匿名函數(shù)定義要點(diǎn):
- 匿名函數(shù)是使用 fun() 關(guān)鍵字定義的
- 該函數(shù)被分配給一個(gè)名為 Fn 的變量
- 該函數(shù)是通過變量名稱來調(diào)用的
函數(shù)可以使用保護(hù)序列來防止輸入無效參數(shù),語法如下:
FunctionName(Pattern1… PatternN) [when GuardSeq1]->
Body;
示例净响,如果 add 函數(shù)被調(diào)用為 add(3),該程序?qū)?huì)出現(xiàn)錯(cuò)誤:
-module(helloworld).
-export([add/1,start/0]).
add(X) when X>3 ->
io:fwrite("~w~n",[X]).
start() ->
add(4).
Erlang 里面函數(shù)是用 函數(shù)名/參數(shù)個(gè)數(shù)
來表示的喳瓣,如果兩個(gè)函數(shù)的函數(shù)名與參數(shù)個(gè)數(shù)都一樣馋贤,他們就是一個(gè)函數(shù)的兩個(gè)分支,必須寫在一起畏陕,分支之間用分號(hào)分割配乓。
如下,clauses.erl 模塊定義一個(gè)函數(shù)的多個(gè)分支 clause 就要用 ;
分割:
-module(clauses).
-export([add/2]).
%% goes into this clause when both A and B are numbers
add(A, B) when is_number(A), is_number(B) ->
A + B;
%% goes this clause when both A and B are lists
add(A, B) when is_list(A), is_list(B) ->
A ++ B.
%% crashes when no above clauses matched.
上面代碼里惠毁,定義了一個(gè)函數(shù):add/2. 這個(gè)函數(shù)有兩個(gè) clause 分支犹芹,一個(gè)是計(jì)算數(shù)字相加的,一個(gè)是計(jì)算字符串相加的鞠绰。
代碼里 when 是一個(gè) Guard 關(guān)鍵字腰埂,匹配模式 Pattern Matching 和保護(hù)序列 Guard 后面講解。
運(yùn)行 add/2 時(shí)會(huì)從上往下挨個(gè)匹配:
$ erl -pa ebin/
Eshell V8.3 (abort with ^G)
1> clauses:add("ABC", "DEF").
"ABCDEF"
2> clauses:add(1, 2).
3
3> clauses:add(1, 2.4).
3.4
4> clauses:add(1, "no").
** exception error: no function clause matching clauses:add(1,"no") (clauses.erl, line 4)
第一個(gè) clause:add 匹配的是第二個(gè) clause蜈膨。 最后一個(gè) clauses:add 都沒匹配上屿笼,崩潰了。
Pattern Matching 模式匹配
- https://www.tutorialspoint.com/erlang/erlang_shell.htm
- https://docs.scala-lang.org/tour/pattern-matching.html
變量通過模式匹配綁定到值翁巍,在 function call, case- receive- try- 和匹配操作符 = 等表達(dá)式中進(jìn)行模式匹配驴一。
模式匹配通常用來簡單嵌套 if-else 結(jié)構(gòu)。
Erlang 里變量的命名有約定灶壶,必須首字母大寫肝断。因?yàn)槭鬃帜感懙模瑫?huì)被認(rèn)為是 atom 原子類型。
Erlang 里沒有賦值語句胸懈,等號(hào) = 是模式匹配符號(hào)担扑,如果 = 左側(cè)跟右側(cè)的值不相等,就叫沒匹配上箫荡,這時(shí)那個(gè) erlang 進(jìn)程會(huì)直接異常崩潰魁亦,不要害怕,erlang 是高容錯(cuò)系統(tǒng)羔挡,程序崩潰挺正常洁奈。
匹配模式中,左則的模式如果和右側(cè)的 term 匹配绞灼,那么模式中未綁定的變量就會(huì)綁定到匹配到的值利术。
Erlang 中的變量在綁定之前是自由的,非綁定變量可以綁定一次任意類型的數(shù)據(jù)低矮。為了支持這種類型系統(tǒng)印叁,Erlang 虛擬機(jī)采用的實(shí)現(xiàn)方法是用一個(gè)帶有標(biāo)簽的機(jī)器字表示所有類型的數(shù)據(jù),這個(gè)機(jī)器字就叫做 term军掂。在 32 位機(jī)器上轮蜕,一個(gè) term 為 32 位寬;在 64 位機(jī)器上蝗锥,一個(gè) term 默認(rèn)為 64 位寬跃洛。由于目前大規(guī)模的服務(wù)器基本上都是 64 位平臺(tái),所以本文下面的討論都基于 64 位平臺(tái)终议。
示例:
1> X.
** 1: variable 'X' is unbound **
2> X = 2.
2
3> X + 1.
3
4> {X, Y} = {1, 2}.
** exception error: no match of right hand side value {1,2}
5> {X, Y} = {2, 3}.
{2,3}
6> Y.
3
程序解析:
-
X
變量開始是未綁定的汇竭,然后綁定到 2 這個(gè)數(shù)值,后面的 X + 1 并非給變量加 1穴张,并沒有模式匹配细燎。 -
{X, Y} = {1, 2}
這里的模式匹配失敗,因?yàn)?X 已經(jīng)綁定皂甘,但和右側(cè)的值不一致玻驻。 -
{X, Y} = {2, 3}
這里的模式匹配成功,因?yàn)橐呀?jīng)綁定的變量 X 和右側(cè)的值一致叮贩,而 Y 變量是沒有綁定的击狮,所以匹配成功對(duì)其綁定為 3。
列如益老,在更多的匹配條件中獲取值:
3> {X, 1, 5} = {2, 1, 5}.
{2,1,5}
4> X.
2
使用匹配來解析 List彪蓬,將第一個(gè)元素綁定到 H, 將其余綁定到 T:
5> [H | T] = [1, 2, 3].
[1,2,3]
6> H.
1
7> T.
[2,3]
可以在函數(shù)中這么遞歸下去,下劃線表示丟棄賦值:
8> [_ | T2] = T.
[2,3]
9> T2.
[3]
10> [_ | T3] = T2.
[3]
11> T3.
[]
Erlang 里面變量是 immutable 的捺萌,可以使用 f()
解綁所有變量档冬,清理之前用過的變量名。
下面重新定義了 Add 函數(shù),現(xiàn)在它只接收一個(gè) tuple 參數(shù)酷誓。然后在參數(shù)列表里做 pattern matching 以獲取 tuple 中的兩個(gè)值披坏,解析到 A,B.
12> f().
ok
13> Add = fun({A, B}) -> A + B end.
#Fun<erl_eval.6.118419387>
14> Add({1, 2}).
3
Erlang 里到處都用匹配的盐数,下面的代碼里棒拂,定義了一個(gè) greet/2 函數(shù):
-module(case_matching).
-export([greet/2]).
greet(Gender, Name) ->
case Gender of
male ->
io:format("Hello, Mr. ~s!~n", [Name]);
female ->
io:format("Hello, Mrs. ~s!~n", [Name]);
_ ->
io:format("Hello, ~s!~n", [Name])
end.
case 的各個(gè)分支是自上往下依次匹配的,如果 Gender 是 atom 'male', 則走第一個(gè)玫氢,如果是 'female' 走第二個(gè)帚屉,如果上面兩個(gè)都沒匹配上,則走第三個(gè)漾峡。
有了匹配模式攻旦,上面的例子改一下,會(huì)更規(guī)整一點(diǎn):
-module(function_matching).
-export([greet/2]).
greet(male, Name) ->
io:format("Hello, Mr. ~s!~n", [Name]);
greet(female, Name) ->
io:format("Hello, Mrs. ~s!~n", [Name]);
greet(_, Name) ->
io:format("Hello, ~s!~n", [Name]).
這個(gè)模塊使用函數(shù)匹配模式生逸,有三個(gè) clause牢屋,與 case 一樣,自上往下依次匹配槽袄。
$ erl -pa ebin/
Eshell V10.4 (abort with ^G)
1> function_matching:greet(female, "Scarlett").
Hello, Mrs. Scarlett!
ok
2>
erl -pa
參數(shù)的意思是 Path Add, 添加目錄到 erlang 以查找目錄列表里的 beam 文件烙无。
bitstring & binary 位串與二進(jìn)制
- http://erlang.org/doc/reference_manual/data_types.html
- http://erlang.org/doc/reference_manual/expressions.html#bit_syntax
- http://erlang.org/doc/programming_examples/bit_syntax.html
比特字符串 bit string 保存在無類型定義的內(nèi)存 untyped memory。
位串包含一系列比特位遍尺,當(dāng)元素都是 8-bit 一個(gè)字節(jié)分組就是二進(jìn)制數(shù)據(jù)皱炉。
位串表達(dá)式的基本格式:
<<>>
<<E1,...,En>>
每個(gè)元素 Ei 指定了一段位串值,大小和類型是可選的:
Ei = Value |
Value:Size |
Value/TypeSpecifierList |
Value:Size/TypeSpecifierList
TypeSpecifierList 類型列表是以下三種組合狮鸭,使用連字符拼接,如 <<D/integer-signed>> = <<80>>.
:
- Type 設(shè)置類型 integer, float, binary, bytes, bitstring, bits, utf8, utf16, utf32
- Signedness 為 integer 設(shè)置符號(hào) signed, unsigned 默認(rèn)值
- Endianness 字節(jié)序多搀,big 默認(rèn)歧蕉、little、 native
Examples:
1> <<10,20>>.
<<10,20>>
2> <<"ABC">>.
<<"ABC">>
3> <<1:2, 2:2>>.
<<6:4>>
上面顯示的 <<6:4>>
表示化位串是 4-bit康铭,值是 6惯退,可以根據(jù)比特位拼接得到 01 拼接 10 結(jié)果為 0110,即十進(jìn)制的 6从藤。
Erlang 沒有字符串類型催跪,字符串通常用 List 表達(dá),如:
1> [97, 98, 99].
"abc"
也可以用二進(jìn)位來表示字符串夷野,更省空間:
1> <<"ABC">>.
<<"ABC">>
使用模式匹配獲取位串的值懊蒸,使用內(nèi)置函數(shù) bit_size 獲取大小:
1> A = <<255, 256, 16#80>>.
<<255,0,128>>
2> <<B,C,D>> = A.
<<255,0,128>>
3> B.
255
4> bit_size(A).
24
5> E = <<B:8>>.
<<"">>
6> F = <<B:16>>.
<<0,255>>
注意悯搔,不能直接從位串獲取指定的子位串 B = <<A:16>>.
骑丸,但是可以先將位串綁定到變量再獲取:
1> A = <<1,1>>.
<<1,1>>
2> B = <<A:16>>.
** exception error: bad argument
in function eval_bits:eval_exp_field1/6 (eval_bits.erl, line 101)
in call from eval_bits:eval_field/3 (eval_bits.erl, line 92)
in call from eval_bits:expr_grp/4 (eval_bits.erl, line 68)
3> <<B:16>> = A.
<<1,1>>
4> C = <<B:16>>.
<<1,1>>
內(nèi)置函數(shù) binary_to_list 可以用于將一個(gè)位字符串轉(zhuǎn)換為列表。
-module(helloworld).
-export([start/0]).
start() ->
Bin1 = <<10,20>>,
X = binary_to_list(Bin1),
io:fwrite("~w",[X]).
執(zhí)行上面的程序通危,輸出結(jié)果如下:
[10,20]
位邏輯操作
bsl (Bit Shift Left),
bsr (Bit Shift Right),
band,
bor,
bxor,
bnot.
Type Casting 類型轉(zhuǎn)換
除了 tuple_to_list 轉(zhuǎn)換成 list 時(shí)都會(huì)盡力轉(zhuǎn)成字符串形式
atom_to_list(hello).
"hello"
binary_to_list(<<"hello">>).
"hello"
binary_to_list(<<104,101,108,108,111>>).
"hello"
float_to_list(7.0).
"7.00000000000000000000e+00"
integer_to_list(77).
"77"
tuple_to_list({a,b,c}).
[a,b,c]
Number 轉(zhuǎn) binary 都轉(zhuǎn)成了字符串
integer_to_binary(77).
<<"77">>
float_to_binary(7.0).
<<"7.00000000000000000000e+00">>
其他的轉(zhuǎn)換
list_to_atom("hello").
hello
list_to_binary("hello").
<<104,101,108,108,111>>
list_to_float("7.000e+00").
7.0
list_to_integer("77").
77
list_to_tuple([a,b,c]).
{a,b,c}
term_to_binary({a,b,c}).
<<131,104,3,100,0,1,97,100,0,1,98,100,0,1,99>>
binary_to_term(<<131,104,3,100,0,1,97,100,0,1,98,100,0,1,99>>).
{a,b,c}
binary_to_integer(<<"77">>).
77
binary_to_float(<<"7.000e+00>>").
7.0
類型判斷
is_atom/1
is_binary/1
is_bitstring/1
is_boolean/1
is_builtin/3
is_float/1
is_function/1 is_function/2
is_integer/1
is_list/1
is_number/1
is_pid/1
is_port/1
is_record/2 is_record/3
is_reference/1
is_tuple/1
Boolean 布爾比較
Erlang 沒有專用的 Boolean 類型攻柠,使用 atom 類型的 true 和 false 兩個(gè)值坠七,作為布爾處理。
1> true and false.
false
2> false or true.
true
3> true xor false.
true
4> not false.
true
5> not (true and true).
false
還有兩個(gè)與 and 和 or 類似的操作:andalso
和 orelse
。區(qū)別是 and 和 or 不論左邊的運(yùn)算結(jié)果是真還是假篇恒,都會(huì)執(zhí)行右邊的操作。而 andalso 和 orelse 是短路的壹士,意味著右邊的運(yùn)算不一定會(huì)執(zhí)行晨继。
來看一下比較:
6> 5 =:= 5.
true
7> 1 =:= 0.
false
8> 1 =/= 0.
true
9> 5 =:= 5.0.
false
10> 5 == 5.0.
true
11> 5 /= 5.0.
false
=:=
和 =/=
分別是嚴(yán)格相等運(yùn)算符和嚴(yán)格不等運(yùn)算符,/=
和 ==
分別是相差很多忍燥,大概相等拧晕。
12> 1 < 2.
true
13> 1 < 1.
false
14> 1 >= 1.
true
15> 1 =< 1.
true
17> 0 == false.
false
18> 1 < false.
true
數(shù)字和 atom 類型是不相等的, 0 /= false梅垄。注意厂捞,小于等于的寫法 =<
,= 在前面队丝,=> 和 <= 兩個(gè)箭頭還有其他的用處靡馁。
雖然不同的類型之間可以比較,也有個(gè)對(duì)應(yīng)的順序机久,但一般情況用不到的:
number < atom < reference < fun < port < pid < tuple < list < bit string
Tuples 元組
Tuple 類型是多個(gè)不同類型的值組合成的類型臭墨。有點(diǎn)類似于 C 語言里的 struct。
語法是:{Element1, Element2, ..., ElementN}
1> X = 10, Y = 4.
4
2> Point = {X,Y}.
{10,4}
上面的 Point 是個(gè) Tuple 類型膘盖,包含了兩個(gè)整形的變量 X 和 Y胧弛。
實(shí)踐中,經(jīng)常在 tuple 的第一個(gè)值放一個(gè) atom 類型侠畔,來標(biāo)注這個(gè) tuple 的含義结缚。這種叫做 tagged tuple:
1> Data1 = {point, 1, 2}.
{point,1,2}
2> Data2 = {rectangle, 20, 30}.
{rectangle,20,30}
后面的代碼如果要處理 Data1 和 Data2 的話,只需要檢查 tuple 的第一項(xiàng)软棺,就知道這個(gè) tuple 是個(gè)點(diǎn)坐標(biāo)红竭,還是個(gè)矩形:
3> case Data1 of
3> {point, X, Y} -> "this is a point";
3> {rectangle, Length, Width} -> "this is a rectangle"
3> end.
"this is a point"
上面用 case 做 pattern matching 模式匹配。
Map 映射
- http://erlang.org/doc/reference_manual/expressions.html#map_expressions
- http://erlang.org/doc/reference_manual/data_types.html
映射是復(fù)合數(shù)據(jù)類型喘落,存放各種鍵值對(duì)茵宪,一個(gè)主鍵 Key 對(duì)應(yīng)一個(gè)值,存放鍵值對(duì)也中元素 Element瘦棋,其數(shù)量就是映射的大邢』稹:
#{Key1=>Value1,...,KeyN=>ValueN}
使用 STDLIB 提供的內(nèi)置函數(shù) BIFs 操作映射:
1> M1 = #{name=>adam,age=>24,date=>{july,29}}.
#{age => 24,date => {july,29},name => adam}
2> maps:get(name,M1).
adam
3> maps:get(date,M1).
{july,29}
4> M2 = maps:update(age,25,M1).
#{age => 25,date => {july,29},name => adam}
5> map_size(M).
3
6> map_size(#{}).
0
Creating Maps
#{}
#{ K => V }
#{ K1 => V1, .., Kn => Vn }
Examples:
M0 = #{}, % empty map
M1 = #{a => <<"hello">>}, % single association with literals
M2 = #{1 => 2, b => b}, % multiple associations with literals
M3 = #{k => {A,B}}, % single association with variables
M4 = #{{"w", 1} => f()}. % compound key associated with an evaluated expression
這里的 A 和 B 可以是任何表達(dá)式。
舊的主鍵值會(huì)被新的替換:
1> #{1 => a, 1 => b}.
#{1 => b }
2> #{1.0 => a, 1 => b}.
#{1 => b, 1.0 => a}
Updating Maps
M#{ K => V }
更新已經(jīng)存在的鍵值兽狭,如果不存在 K 主鍵就觸發(fā)異常憾股,返回一個(gè)新的映射:
M#{ K := V }
Examples:
M0 = #{},
M1 = M0#{a => 0},
M2 = M1#{a => 1, b => 2},
M3 = M2#{"function" => fun() -> f() end},
M4 = M3#{a := 2, b := 3}. % 'a' and 'b' was added in `M1` and `M2`.
More examples:
1> M = #{1 => a}.
#{1 => a }
2> M#{1.0 => b}.
#{1 => a, 1.0 => b}.
3> M#{1 := b}.
#{1 => b}
4> M#{1.0 := b}.
** exception error: bad argument
Maps in Patterns
#{ K := V } = M
映射 M 中的 K 必須是 guard expression鹿蜀,并綁定了變量。如果 V 是沒有綁定的值服球,就會(huì)綁定到 K茴恰,如果 V 是綁定的值,必需和映射 M 中的主鍵 K 的值匹配。
Example:
1> M = #{"tuple" => {1,2}}.
#{"tuple" => {1,2}}
2> #{"tuple" := {1,B}} = M.
#{"tuple" => {1,2}}
3> B.
2.
相似地,多值模式匹配:
#{ K1 := V1, .., Kn := Vn } = M
主鍵 K1 .. Kn 是字面表達(dá)式或是綁定的變量,如果,所有主鍵在 M 中存在都匹配,那么 V1 .. Vn 匹配到相應(yīng)主鍵的對(duì)應(yīng)值。
模式匹配滿足以下任一條件即為失敗:
- A badmatch exception.
- Or resulting in the next clause being tested in function heads and case expressions.
映射的模式匹配只可用 := 分隔符號(hào),順序是不重要的,重復(fù)的主鍵也是可以的,空映射也可以匹配榜旦,只要以下的 Expr 是映射類型:
#{ K := V1, K := V2 } = M
#{} = Expr
用表達(dá)式作為主鍵澡屡,要求 List 已經(jīng)綁定變量:
#{{tag,length(List)} := V} = Map
Matching Syntax
%% only start if not_started
handle_call(start, From, #{ state := not_started } = S) ->
...
{reply, ok, S#{ state := start }};
%% only change if started
handle_call(change, From, #{ state := start } = S) ->
...
{reply, ok, S#{ state := changed }};
List 列表
List 就是我們經(jīng)常說的鏈表铣墨,數(shù)據(jù)結(jié)構(gòu)里學(xué)的那個(gè)屡律。但 List 類型在 Erlang 里使用極其頻繁,因?yàn)橛闷饋砗芊奖恪?/p>
List 可以包含各種類型的值:
1> [1, 2, 3, {numbers,[4,5,6]}, 5.34, atom].
[1,2,3,{numbers,[4,5,6]},5.34,atom]
上面這個(gè) list 包含了 3 個(gè)數(shù)值類型和一個(gè) tuple,一個(gè)浮點(diǎn)數(shù)繁成,一個(gè) atom 類型。
來看看這個(gè):
2> [97, 98, 99].
"abc"
臥槽這什么意思面睛?尊搬!因?yàn)?Erlang 的 String 類型其實(shí)就是 List叁鉴!所以 erlang shell 自動(dòng)給你顯示出來了。就是說如果你這么寫 "abc" 等效 [97, 98, 99]。
注意,鏈表存儲(chǔ)空間比純字符串?dāng)?shù)組大舞肆,拼接等操作也費(fèi)時(shí)椿胯,所以一般使用字符串的時(shí)候筷登,用 Erlang 的 Binary 類型,這樣寫:<<"abc">>
內(nèi)存消耗就小很多了压状。
開始你可能不大明白 tuple 跟 list 的區(qū)別仆抵,這樣吧:
- 當(dāng)你知道你的數(shù)據(jù)結(jié)構(gòu)有多少項(xiàng)的時(shí)候,用 Tuple种冬;
- 當(dāng)你需要?jiǎng)討B(tài)長度的數(shù)據(jù)結(jié)構(gòu)時(shí)镣丑,用 List。
List 處理:
5> [1,2,3] ++ [4,5].
[1,2,3,4,5]
6> [1,2,3,4,5] -- [1,2,3].
[4,5]
7> [2,4,2] -- [2,4].
[2]
8> [2,4,2] -- [2,4,2].
[]
9> [] -- [1, 3].
[]
11> hd([1,2,3,4]).
1
12> tl([1,2,3,4]).
[2,3,4]
-
++
運(yùn)算符是往左邊的那個(gè) List 尾部追加右邊的 List娱两。 -
--
是移除操作符莺匠,如果左邊的 List 里不包含需要移除的值,也沒事兒十兢。不要拿這種東西來做面試題趣竣,這樣會(huì)沒朋友的。 -
hd/1
函數(shù)是獲取 Head旱物。 -
tl/1
函數(shù)是獲取 Tail遥缕,和 hd/1 都是 Erlang 內(nèi)置函數(shù) BIF - Built-In-Function。
鏈表嘛你知道的宵呛,往鏈表尾部追加单匣,需要先遍歷這個(gè)鏈表,找到鏈表的尾部宝穗。 所以 "abc" ++ "de" 這種的操作的復(fù)雜度户秤,取決于前面 "abc" 的長度。
第一行里你也看到了逮矛,List 的追加操作會(huì)有性能損耗鸡号,lists:append/2
跟 ++ 是一回事兒,所以我們需要一個(gè)從頭部插入 List 的操作:
13> List = [2,3,4].
[2,3,4]
14> NewList = [1|List].
[1,2,3,4]
15> [1, 2 | [0]].
[1,2,0]
16> [1, 2 | 0].
[1,2|0]
注意這個(gè) |
的左邊應(yīng)該放元素须鼎,右邊應(yīng)該放 List鲸伴。左邊元素有好幾個(gè)的話,erlang 會(huì)幫你一個(gè)一個(gè)的插到頭部晋控。如果右邊放的不是 List挑围,像 [1, 2 | 0]
這種叫不適 improper list。雖然你可以生成這種列表糖荒,但不要這么做,代碼里出現(xiàn)這種一般就是個(gè) bug模捂,忘了這種用法吧捶朵。
List 可以分解為 [ 第一個(gè)元素 | 剩下的 List ]蜘矢,仔細(xì)看一下這幾行體會(huì)一下:
20> [1 | []].
[1]
21> [2 | [1 | []]].
[2,1]
22> [3 | [2 | [1 | []] ] ].
[3,2,1]
實(shí)踐中我們經(jīng)常會(huì)從一個(gè) List 取出需要的那些元素,然后做處理综看,最后再將處理過的元素重新構(gòu)造成一個(gè)新的元素品腹。
你馬上就想到了 map,reduce红碑。在 Erlang 里舞吭,我們可以用列表推理
List Comprehensions 語法,很方便的做一些簡單的處理析珊。
下例羡鸥,取出 [1,2,3,4] 每個(gè)元素,然后乘 2忠寻,返回值再組成一個(gè)新的 List惧浴,后面再取出列表里所有偶數(shù)。
1> [2*N || N <- [1,2,3,4]].
[2,4,6,8]
2> [X || X <- [1,2,3,4,5,6,7,8,9,10], X rem 2 =:= 0].
[2,4,6,8,10]
Atoms 原子類型
Erlang 里面有 atom 原子類型奕剃,它使用的內(nèi)存很小衷旅,所以常用來做函數(shù)的參數(shù)和返回值。參加 pattern matching 的時(shí)候纵朋,運(yùn)算也非呈炼ィ快速。
在其他沒有 atom 的語言里操软,你可能用過 constant 之類的東西嘁锯,一個(gè)常量需要對(duì)應(yīng)一個(gè)數(shù)字值或者其他類型的值。
在 Erlang 里 atom 真是抬頭不見低頭見寺鸥,可以通過 atom 來表示各種意義的常量猪钮。在其他語言,例如 C/C++ 中使用 #define 宏定義胆建,enum 枚舉烤低,或者用 const 常量等方法實(shí)現(xiàn)類似的功能。
但是笆载,使用這些方法的時(shí)候扑馁,總會(huì)覺得不是太舒服,比如使用 #define 宏定義和 const 常量凉驻,除了本來就頭痛的給宏或常量命名之外腻要,還要真正填上一個(gè)值,為了讓這些值不沖突涝登,又是一件頭痛的事情了雄家。如果用字符串吧,那么每次匹配的時(shí)候還要做低效的字符串操作胀滚。
比如:
const int red = 1;
const int green = 2;
const int blue = 3;
但多了這個(gè)映射趟济,其實(shí)用起來不大方便乱投,后面對(duì)應(yīng)的值 1, 2顷编,3 一般只是用來比較戚炫,具體是什么值都關(guān)系不大。所以有了 atom 就很方便了媳纬,我們從字面上就能看出双肤,這個(gè)值是干嘛的:
1> red.
red
atom 類型支持的寫法:
1> atom.
atom
2> atoms_rule.
atoms_rule
3> atoms_rule@erlang.
atoms_rule@erlang
4> 'Atoms can be cheated!'.
'Atoms can be cheated!'
5> atom = 'atom'.
atom
包含空格等特殊字符的 atom 需要用單引號(hào)括起來。 Erlang 里變量的命名必須首字母大寫钮惠,小寫起頭是 atom 原子類型茅糜。
需要注意的是:在一個(gè) erlang vm 里,可創(chuàng)建的 atom 的數(shù)量是有限制的萌腿,默認(rèn)是 1,048,576限匣,因?yàn)?erlang 虛擬機(jī)創(chuàng)建 atom 表也是需要內(nèi)存的。一旦創(chuàng)建了某個(gè) atom毁菱,它就一直存在那里了米死,不會(huì)被垃圾回收。不要在代碼里動(dòng)態(tài)的做 string -> atom 的類型轉(zhuǎn)換贮庞,這樣最終會(huì)使你的 erlang atom 爆表峦筒。比如在你的接口邏輯處理的部分做 to atom 的轉(zhuǎn)換的話,別人只需要用不一樣的參數(shù)不停地調(diào)用你的接口窗慎,就可以攻擊你物喷。
Guards 保護(hù)序列
在函數(shù)定義中,可以使用 when 加入保持序列遮斥。
假設(shè)峦失,learn-you-some-erlang 的作者那邊 16 歲才能"開車" (笑). 那我們寫個(gè)函數(shù)判斷一下,某個(gè)人能不能開車术吗?
old_enough(0) -> false;
old_enough(1) -> false;
old_enough(2) -> false;
...
old_enough(14) -> false;
old_enough(15) -> false;
old_enough(_) -> true.
上面這個(gè)又點(diǎn)太繁瑣了尉辑,所以我們得另想辦法:
old_enough(X) when X >= 16 -> true;
old_enough(_) -> false.
然后作者又說了,超過 104 歲的人较屿,禁止開車:
right_age(X) when X >= 16, X =< 104 ->
true;
right_age(_) ->
false.
注意 when 語句里隧魄,,
逗號(hào)表示 and, ;
分號(hào)表示 or, 如果你想用短路運(yùn)算符的話,用 andalso 和 orelse, 這么寫:
right_age(X) when X >= 16 andalso X =< 104 -> true;
Records 記錄體
- http://erlang.org/doc/reference_manual/records.html
- http://erlang.org/doc/programming_examples/records.html
- https://www.cnblogs.com/me-sa/archive/2011/12/31/erlang0027.html
- http://erlang.org/doc/getting_started/record_macros.html
前面講過 tagged tuple隘蝎,但它用起來還不夠方便购啄,因?yàn)闆]有個(gè)名字,也不好訪問其中的變量嘱么。
-record(Name,{
key1 = Default1,
key2 = Default2,
key3, %% 默認(rèn)值是 undefined
...
}).
Erlang 的 record 類型可以提個(gè)名字訪問:
-module(records).
-export([get_user_name/1,
get_user_phone/1]).
-record(user, {
name,
phone
}).
get_user_name(#user{name=Name}) ->
Name.
get_user_phone(#user{phone=Phone}) ->
Phone.
編譯測試:
$ erl
Eshell V8.3 (abort with ^G)
1> c(records).
{ok,records}
2> rr(records).
[user]
4> Shawn = #user{name = <<"Shawn">>, phone = <<"18253232321">>}.
#user{name = <<"Shawn">>,phone = <<"18253232321">>}
5> records:get_user_phone(Shawn).
<<"18253232321">>
6> records:get_user_name(Shawn).
<<"Shawn">>
7> records:get_user_name({user, <<"Shawn">>, <<"18253232321">>}).
<<"Shawn">>
9> Shawn#user.name.
<<"Shawn">>
10> #user.name.
2
程序解釋:
- 其實(shí)
#user{}
相當(dāng){user, name, phone}
狮含,是第一個(gè)元素為 user 的 tagged tuple。 -
#user.name
是這個(gè) tuple 里 name 字段的位置號(hào) 2。 - Record 字段的位置 Index 等都是約定從 1 開始的几迄。
- Shawn#user.name 的意思是取 Shawn 里的第 2 個(gè)元素表蝙。
定義記錄 record 可以包含在 .erl 源代碼或在 .hrl 文件中:
-record(todo,{status=reminder,who=joe,text}).
在 Erlang shell 創(chuàng)建 record 實(shí)例,必須先讀取記錄的定義乓旗,使用命令 rr(read records):
1>rr("records.hrl").
注意: rr 方法支持通配符,比如 rr("*")
集索,使用 rf(record free) 函數(shù)釋放掉記錄的定義屿愚。
有個(gè)內(nèi)置函數(shù) is_record(Term, RecordTag) 判斷記錄類型:
is_person(P) when is_record(P, person) ->
true;
is_person(_P) ->
false.
foo(P) when is_record(P, person) -> a_person;
foo(_) -> not_a_person.
Record 是一個(gè)編譯時(shí)的功能,在 Erlang VM 中并沒有專門的數(shù)據(jù)類型务荆,在線上解決問題有時(shí)候會(huì)遇到要在 shell 中使用 record妆距。在 shell 中使用 rd 命令構(gòu)造 record 定義,構(gòu)造 record 定義編寫 ets:match 的匹配模式就方便多了函匕。另一種方法是直接使用 record 對(duì)應(yīng)的 tuple 結(jié)構(gòu)娱据。
Eshell V5.9 (abort with ^G)
1> rd(film ,{ director, actor, type, name,imdb}).
film
2> F =#film{}.
#film{director = undefined,actor = undefined,
type = undefined,name = undefined,imdb = undefined}
3> F#film.type.
undefined
4> F#film.type=23.
* 1: illegal pattern
5> F2 =F#film{type=23}.
#film{director = undefined,actor = undefined,type = 23,
name = undefined,imdb = undefined}
Record 通過 # 符號(hào)來創(chuàng)建,更新 Record 和創(chuàng)建 Record 很類似:
Opts = #record{name=<<"Jean">>, Phone=<<"020-12345">>},
NewOpts = Opts#record{name="Jim"}.
這里首先創(chuàng)建一個(gè) record 綁定到 Opts 變量盅惜,然后 NewOpts 創(chuàng)建了一個(gè) Opts 的副本中剩,并指定新的名字綁定到 NewOpts。
模式匹配抒寂,假如定義 person 記錄结啼,下面的模式匹配中會(huì)將 P3 中的名子綁定到 Name 變量。
> rd(person, {name = "", phone = [], address}).
person
> P3 = #person{name="Joe", phone=[0,0,7], address="A street"}.
#person{name = "Joe",phone = [0,0,7],address = "A street"}
> #person{name = Name} = P3, Name.
"Joe"
Recursive 遞歸
遞歸是 Erlang 的重要組成部分屈芜。
以下實(shí)現(xiàn)階乘程序來了解簡單的遞歸郊愧。
-module(helloworld).
-export([fac/1,start/0]).
fac(N) when N == 0 -> 1;
fac(N) when N > 0 -> N*fac(N-1).
start() ->
X = fac(4),
io:fwrite("~w",[X]).
以遞歸一個(gè)更有效的方法可以用于確定一個(gè)列表的長度,現(xiàn)在來看看一個(gè)簡單的例子井佑。列表中有多個(gè)值属铁,如[1,2,3,4]。
讓我們用遞歸的方法來看看如何能夠得到一個(gè)列表的長度躬翁。
-module(helloworld).
-export([len/1,start/0]).
len([]) -> 0;
len([_|T]) -> 1 + len(T).
start() ->
X = [1,2,3,4],
Y = len(X),
io:fwrite("~w",[Y]).
上述程序關(guān)鍵點(diǎn):
- 第一個(gè)函數(shù)
len([])
用于特殊情況的條件:如果列表為空焦蘑。 -
[H|T]
模式來匹配一個(gè)或多個(gè)元素的列表,如長度為 1 的列表可以定義為[X|[]]
姆另,而長度為 2 的列表可以定義為[X|[Y|[]]]
喇肋。
注意,第二元素是列表本身迹辐。這意味著我們只需要計(jì)數(shù)第一個(gè)蝶防,函數(shù)可以調(diào)用它本身在第二元素上。在列表給定每個(gè)值的長度計(jì)數(shù)為 1 明吩。
有個(gè)比喻可以幫你理解尾遞歸
與遞歸的區(qū)別
:
假設(shè)玩一個(gè)游戲间学,你需要去收集散落了一路,并通向遠(yuǎn)方的硬幣。
于是你一個(gè)一個(gè)的撿低葫,一邊撿一邊往前走详羡,但是你必須往地上撒些紙條做記號(hào),因?yàn)椴蛔鲇浱?hào)你就忘了回來的路嘿悬。于是你一路走实柠,一路撿,一路撒紙條善涨。等你撿到最后一個(gè)硬幣時(shí)窒盐,你開始沿著記號(hào)回來了,一路走钢拧,一路撿紙條(保護(hù)環(huán)境)蟹漓。等回到出發(fā)點(diǎn)時(shí),你把硬幣裝你包里源内,把紙條扔進(jìn)垃圾桶葡粒。
這就是非尾遞歸,紙條就是你的調(diào)用棧膜钓,是內(nèi)存記錄嗽交。
下次再玩這個(gè)游戲時(shí),你學(xué)聰明了呻此,你直接背著包過去了轮纫,一路走,一路撿焚鲜,一路往包里塞掌唾。等到了終點(diǎn)時(shí),最后一個(gè)硬幣進(jìn)包了忿磅,任務(wù)完成了糯彬,你不回來了!
這就是尾遞歸葱她,省去了調(diào)用棧的消耗撩扒。
Loops 循環(huán)控制
Erlang 中沒有可直接使用的循環(huán)控制語句,須使用遞歸技術(shù)在 Erlang 中來實(shí)現(xiàn) while/for 等語句吨些。
-module(helloworld).
-export([while/1,while/2, start/0]).
while(L) -> while(L,0).
while([], Acc) -> Acc;
while([_|T], Acc) ->
io:fwrite("~w~n",[Acc]),
while(T,Acc+1).
start() ->
X = [1,2,3,4],
while(X).
此循環(huán)程序定義了遞歸函數(shù)模擬 while 循環(huán)搓谆,在主函數(shù)輸入一個(gè)數(shù)值列表,列表綁定到變量 X 中豪墅。在 while 函數(shù)中泉手,利用中間變量 Acc 保存從列表取出的值,然后遞歸調(diào)用 while 函數(shù)偶器。
-module(helloworld).
-export([for/2,start/0]).
for(0,_) ->
[];
for(N,Term) when N > 0 ->
io:fwrite("Hello~n"),
[Term|for(N-1,Term)].
start() ->
for(5,1).
上述程序?qū)崿F(xiàn) for 循環(huán)的關(guān)鍵點(diǎn):
- 定義一個(gè)遞歸函數(shù)來實(shí)例和執(zhí)行 for 循環(huán)斩萌;
- 使用 for 函數(shù)以確保 N 或限制的值是正值缝裤;
- 遞歸地調(diào)用 for 函數(shù),通過在每一次遞歸后減少 N 的值颊郎。
Module 模塊定義
模塊是在一個(gè)文件重新組合的函數(shù)集合憋飞,在 Erlang 所有函數(shù)必須在模塊定義。模塊的名稱必須在模塊代碼的第一行姆吭,并且和文件名一致榛做。
大部分像算術(shù),邏輯和布爾操作符的基本函數(shù)已經(jīng) Erlang 內(nèi)部集成提供并且可以直接調(diào)用内狸,因?yàn)樵谶\(yùn)行程序時(shí)的默認(rèn)模塊被加載瘤睹。一個(gè)模塊中使用定義的所有其他函數(shù)需要使用形式 Module:Function (參數(shù)) 來調(diào)用。
下面的程序顯示了一個(gè)叫 helloworld 模塊的一個(gè)例子答倡。
-module(helloworld).
-author("TutorialPoint").
-version("1.0").
-export([start/0]).
-import(io,[fwrite/1]).
start() ->
io:fwrite("Hello World").
模塊定義了 author、 version 兩個(gè)標(biāo)簽屬性驴党,可以按 -Tag(Value)
格式定義瘪撇。
模塊導(dǎo)出函數(shù) export 指定一個(gè)導(dǎo)出列表,這里只導(dǎo)出一個(gè) start 函數(shù)港庄,參數(shù)個(gè)數(shù)為 0 個(gè)倔既。導(dǎo)入語句類似,它指定導(dǎo)入的模塊和函數(shù)列表鹏氧。所以渤涌,現(xiàn)在每當(dāng)調(diào)用 fwrite 函數(shù),不必每次都要帶上模塊的名稱把还。
導(dǎo)入模塊和函數(shù) -import(io,[fwrite/1]).
格式類似導(dǎo)出实蓬,它需要指定導(dǎo)入的模塊。Erlang 沒有全部導(dǎo)入的方式吊履,但是可以在運(yùn)行 erl -pa .\ebin 指定編譯后的程序目錄安皱,這樣 Erlang 會(huì)自動(dòng)查找引用到的函數(shù)。
然后你用 erlc 編譯
mkdir -p ./ebin
erlc -o ebin helloworld.erl
編譯后的 beam 文件會(huì)在 ebin 目錄下艇炎,然后你啟動(dòng) erlang shell:
$ erl -pa ./ebin
Eshell V8.3 (abort with ^G)
1> helloworld:start().
3
2> helloworld:start().
4
erl -pa 參數(shù)的意思是 Path Add, 添加 beam 文件目錄到 erlang 以自動(dòng)查找編譯好的程序酌伊。就是說,你運(yùn)行 helloworld:start(). 的時(shí)候缀踪,Erlang 發(fā)現(xiàn) module 'helloworld' 沒加載居砖,就在那些查找目錄里找 helloworld.beam,然后加載進(jìn)來驴娃。
Error 錯(cuò)誤處理
- 編譯運(yùn)行 http://gashero.yeax.com/?p=67
- http://erlang.org/doc/getting_started/robustness.html
- http://erlang.org/doc/reference_manual/errors.html
常見錯(cuò)誤碼意義:
-
eacces
Missing search or write permissions for the parent directories of Dir. -
eexist
目錄不是空目錄奏候; -
enoent
目錄不存在; -
enotdir
不是目錄托慨,一些系統(tǒng)會(huì)返回 enoent鼻由; -
einval
試圖刪除當(dāng)前目錄暇榴,一些系統(tǒng)會(huì)返回 eacces; -
badarg
參數(shù)錯(cuò)誤蕉世; -
badarith
運(yùn)算錯(cuò)誤蔼紧,atithmetic 運(yùn)算,例如將一個(gè)整數(shù)和一個(gè) atom 相加狠轻。 -
{badmatch, V}
模式匹配錯(cuò)誤 -
function_clause
該錯(cuò)誤信息表示找不到匹配的函數(shù)奸例。例如,不到匹配的分支向楼,會(huì)拋出 function_clause查吊。 -
{case_clause, V}
case 表達(dá)式找不到匹配的分支。一般要把_
加到最后的分支中湖蜕,作為容錯(cuò)或者其它逻卖。 -
if_clause
if 表達(dá)式是 case 表達(dá)式的一種特殊方式,要求至少有一個(gè)分支測試條件的結(jié)果為 true昭抒,否則會(huì)引發(fā)錯(cuò)誤评也。 -
undef
調(diào)用未定義的函數(shù)或者模塊時(shí),返回該錯(cuò)誤信息灭返。 -
noproc
進(jìn)程不存在盗迟,例如 gen_server:call 一個(gè)不存在的進(jìn)程。 -
system_limit
超出系統(tǒng)上限熙含,如 atom罚缕,ets,port怎静,process 等邮弹。
異常處理
在開發(fā)中可使用try,catch捕獲異常,同時(shí)也調(diào)用erlang:get_stacktrace(),獲取棧信息蚓聘,定位錯(cuò)誤肠鲫。
try:
%% 業(yè)務(wù)代碼
Exprs
catch
Calss:Reason ->
%% 異常處理代碼,
%% Calss為異常類型或粮,Reason為異常原因
ok
end.
一個(gè)簡單的例子:
-module(test).
-export([add/2]).
add(A,B) ->
try
A + B
catch
Class:Reason ->
io:format("Class:~p,Reason:~p~nstacktrace:~n~p",
[Class,Reason,erlang:get_stacktrace()]),
error
end.
查詢當(dāng)前日志記錄等級(jí)信息:
>erl -kernel logger_level info -s init stop
=PROGRESS REPORT==== 15-Jun-2020::17:03:16.209000 ===
application: kernel
started_at: nonode@nohost
=PROGRESS REPORT==== 15-Jun-2020::17:03:16.222000 ===
application: stdlib
started_at: nonode@nohost
Eshell V10.4 (abort with ^G)
1>