Erlang/OTP OCP 面向并發(fā)編程入門

Erlang Introduction

秀秀我的開發(fā)環(huán)境

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 語言特性

  1. 簡單小巧

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 中是以位串 bitstringList 表達(dá)的。沒有 Boolean 類型凝垛,使用 atoms 原子類型的 true & false 替代懊悯。

  1. 模式匹配

在 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)]有任何干系铛碑。

  1. 變量單次賦值

一個(gè)匪夷所思的特性,變量竟然只能單次賦值虽界!是的 Erlang 中變量一旦綁定某個(gè)數(shù)值以后汽烦,就不能再次綁定,這樣做的好處是便于調(diào)試出錯(cuò)莉御,更深層次的原因是 Erlang 為并發(fā)設(shè)計(jì)撇吞,如果變量可以修改,那么就涉及到資源的加鎖解鎖等問題礁叔,當(dāng)發(fā)生錯(cuò)誤時(shí)牍颈,某個(gè)變量是什么就永遠(yuǎn)是什么,不用順藤摸瓜的查找誰修改過它琅关,省了好多事情煮岁。唯一的麻煩就是需要一個(gè)信的變量時(shí),你必須再為它想一個(gè)名字。

  1. 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)行單元測試铺董。

  1. 靈活多樣的錯(cuò)誤處理

Erlang 最初為電信產(chǎn)品的開發(fā)巫击,這樣的目的,決定了其對(duì)錯(cuò)誤處理的嚴(yán)格要求精续。Erlang 中提供一般語言所提供的 exception坝锰,catch,try…catch 等語法重付,同時(shí) Erlang 支持 LinkMonitor 兩種機(jī)制顷级,我們可以將 Process 連接起來,讓他們組成一個(gè)整體确垫,某個(gè) Process 出錯(cuò)弓颈,或退出時(shí),其他 Process 都具有得知其推出的能力删掀。而 Monitor 顧名思義翔冀,可以用來監(jiān)控某個(gè) Process,判斷其是否退出或出錯(cuò)披泪。所有的這些 Erlang 都提供內(nèi)在支持纤子,我們快速的開發(fā)堅(jiān)固的產(chǎn)品,不再是奢望付呕。

  1. 代碼熱替換

你的產(chǎn)品想不間斷的更新么计福?Erlang 可以滿足你這個(gè)需求,Erlang 會(huì)在運(yùn)行時(shí)自動(dòng)將舊的模塊進(jìn)行替換徽职。一切都靜悄悄。

  1. 天生的分布式

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ì)支持分布式操作。

  1. 超強(qiáng)的并發(fā)性

由于采用其自身 Process描焰,而沒有采用操作系統(tǒng)的進(jìn)程和線程媳否,我們可以創(chuàng)建大規(guī)模的并發(fā)處理,同時(shí)還簡化了我們的編程復(fù)雜度荆秦。我們可以通過幾十行代碼實(shí)現(xiàn)一個(gè)并發(fā)的TCP服務(wù)器篱竭,這在其他語言中都想都不敢想!

  1. 多核支持

Erlang讓您的應(yīng)用支持多個(gè)處理器步绸,您不需要為不同的硬件系統(tǒng)做不同的開發(fā)掺逼。采用Erlang將最大限度的發(fā)揮你的機(jī)器性能。

  1. 跨平臺(tái)

如同JAVA一樣靡努,Erlang 支持跨平臺(tái)(其目前支持linux坪圾,mac,windows等19種平臺(tái))惑朦,不用為代碼的移植而頭疼兽泄。

我們僅僅需要了解平臺(tái)的一些特性,對(duì)運(yùn)行時(shí)進(jìn)行優(yōu)化漾月。

  1. 開源

開源是我非常喜歡的一個(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í)間和精力去完成原本很直接的事情蜜葱。

  1. Erlang陡峭的學(xué)習(xí)曲線

大多數(shù)情況下全景,學(xué)一門新語言,大部分基本概念都可以靠其他語言的經(jīng)驗(yàn)快速理解牵囤,比如你如果學(xué)過 C爸黄,再學(xué) Java 不是什么難事滞伟。但是 erlang 正相反,你要先設(shè)法忘掉其他語言的一些概念炕贵,比如變量這個(gè)概念梆奈,在 erlang 中是不存在的。這些概念是如此地根深蒂固称开,讓我很難 think in erlang亩钟,以至于讀完一本 erlang 的教程,我仍然寫不出來斐波那契數(shù)列的程序.

  1. 這門語言沒有什么 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 官方文檔提供以下內(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 模式匹配

變量通過模式匹配綁定到值翁巍,在 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)制

比特字符串 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 類似的操作:andalsoorelse。區(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 映射

映射是復(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 記錄體

前面講過 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ò)誤處理

常見錯(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>
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末导饲,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子氯材,更是在濱河造成了極大的恐慌渣锦,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,542評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件氢哮,死亡現(xiàn)場離奇詭異袋毙,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)冗尤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門听盖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來胀溺,“玉大人,你說我怎么就攤上這事皆看〔治耄” “怎么了?”我有些...
    開封第一講書人閱讀 163,912評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵腰吟,是天一觀的道長无埃。 經(jīng)常有香客問我,道長毛雇,這世上最難降的妖魔是什么嫉称? 我笑而不...
    開封第一講書人閱讀 58,449評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮灵疮,結(jié)果婚禮上织阅,老公的妹妹穿的比我還像新娘。我一直安慰自己震捣,他們只是感情好蒲稳,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著伍派,像睡著了一般。 火紅的嫁衣襯著肌膚如雪剩胁。 梳的紋絲不亂的頭發(fā)上诉植,一...
    開封第一講書人閱讀 51,370評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音昵观,去河邊找鬼晾腔。 笑死,一個(gè)胖子當(dāng)著我的面吹牛啊犬,可吹牛的內(nèi)容都是我干的灼擂。 我是一名探鬼主播,決...
    沈念sama閱讀 40,193評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼枚赡,長吁一口氣:“原來是場噩夢啊……” “哼遮咖!你這毒婦竟也來了效斑?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,074評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤峻贮,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后应闯,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體纤控,經(jīng)...
    沈念sama閱讀 45,505評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評(píng)論 3 335
  • 正文 我和宋清朗相戀三年碉纺,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了船万。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片刻撒。...
    茶點(diǎn)故事閱讀 39,841評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖耿导,靈堂內(nèi)的尸體忽然破棺而出声怔,到底是詐尸還是另有隱情,我是刑警寧澤碎节,帶...
    沈念sama閱讀 35,569評(píng)論 5 345
  • 正文 年R本政府宣布捧搞,位于F島的核電站,受9級(jí)特大地震影響狮荔,放射性物質(zhì)發(fā)生泄漏胎撇。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評(píng)論 3 328
  • 文/蒙蒙 一殖氏、第九天 我趴在偏房一處隱蔽的房頂上張望晚树。 院中可真熱鬧,春花似錦雅采、人聲如沸爵憎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽宝鼓。三九已至,卻和暖如春巴刻,著一層夾襖步出監(jiān)牢的瞬間愚铡,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評(píng)論 1 269
  • 我被黑心中介騙來泰國打工胡陪, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留沥寥,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,962評(píng)論 2 370
  • 正文 我出身青樓柠座,卻偏偏與公主長得像邑雅,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子妈经,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評(píng)論 2 354