咱們在這裏很高興地和你們分享 Hamler 0.2 版本發佈的消息!html
Hamler 是一門構建在 Erlang 虛擬機(VM)上的 Haskell 風格的強類型(Strongly-typed)編程語言,獨特意結合了編譯時的類型檢查推導,與對運行時高併發和軟實時能力的支持。git
Hamler 0.2 現已支持大部分 Erlang 的併發編程特性,包括基於 Actor Model 的 Message Passing Concurrency 和 OTP Behaviours。github
1974年,卡爾-休伊特教授發表了論文《Actor model of computation》。文中,他闡述了 Actor 做爲一個計算實體,它會對收到的消息做出迴應,並能夠併發地進行如下操做:編程
隨着多核計算和大規模分佈式系統的興起,Actor 模型因其自然的併發性、並行性和分佈式變得愈來愈重要。安全
Hamler/Erlang 中的 Actor 被定義爲一個進程,它的工做方式就像一個 OS 進程。每一個進程都有本身的內存,由一個 Mailbox、一個 Heap、一個 Stack 和一個包含進程信息的 Process Control Block(PCB) 組成。服務器
Erlang 中的進程是很是輕量的,咱們能夠在一個正在運行的 Erlang 虛擬機上快速建立數百萬個進程。併發
"Message passing concurrency(MPS)是兩個或多個進程之間沒有共享資源狀況下的併發,它們經過僅傳遞消息進行通訊。" Actor Model 就是 MPS 模型的一個實現。編程語言
參考資料:分佈式
Ping/Pong示例:
import Prelude import Control.Process (selfPid) go :: Process () go = do self <- selfPid pid <- spawn loop pid ! (self, :ping) receive :pong -> println "Pong!" pid ! :stop loop :: Process () loop = receive (from, :ping) -> do println "Ping!" from ! :pong loop :stop -> return ()
Receive ... after示例:
go :: Process () go = do pid <- spawn recvAfter pid ! :foo recvAfter :: Process () recvAfter = receive :bar -> println "recv bar" after 1000 -> println "timeout"
Selective Receive 示例:
go :: Process () go = do pid <- spawn selectiveRecv pid ! :bar pid ! :foo selectiveRecv :: Process () selectiveRecv = do receive :foo -> println "foo" receive :bar -> println "bar"
Hamler 採用類型類(TypeClass)實現 OTP Behaviour。
TypeClass 定義了具備相似 operation 的一組類型。在咱們的實現中,使用 typeclass 來對不一樣 OTP Behaviour 的類型進行區分。經過爲每一個 Behavour 定義一個 typeclass 的方式,咱們對這些 Behaviour 作了某種程度上的抽象,並在必定程度上增長了類型約束。
Generic Server Behaviour 是對 客戶端-服務器 關係模型中服務器的抽象。如圖所示,在該模型的服務器側,全部的通用操做均可以被封裝成爲一個模塊。與 Erlang 同樣,Hamler 將其封裝爲 GenServer 的模塊。不一樣的是在 Hamler 中 GenServer 由類型類進行定義,它全部的回調函數和參數都必須受到類型約束,它在具有 Erlang 的 gen_server
特性的同時,也保證了類型的安全。以 handleCall
和 handleCast
爲例:
參考資料 Erlang gen_server Behaviour。
GenServer Typeclass
class GenServer req rep st | req -> rep, rep -> st, st -> req where handleCall :: HandleCall req rep st handleCast :: HandleCast req rep st
A simple Server Example
module Demo.Server ( start , inc , dec , query ) where import Prelude import Control.Behaviour.GenServer ( class GenServer , HandleCall , HandleCast , Init , startLinkWith , initOk , call , cast , noReply , reply , shutdown ) import System.IO (println) data Request = Inc | Dec | Query data Reply = QueryResult Integer data State = State Integer name :: Atom name = :server start :: Process Pid start = startLinkWith name (init 20) inc :: Process () inc = cast name Inc dec :: Process () dec = cast name Dec query :: Process Integer query = do QueryResult i <- call name Query return i instance GenServer Request Reply State where handleCall = handleCall handleCast = handleCast init :: Integer -> Init Request State init n = initOk (State n) handleCall :: HandleCall Request Reply State handleCall Query _from (State i) = do println "Call: Query" reply (QueryResult i) (State i) handleCall _req _from st = shutdown :badRequest st handleCast :: HandleCast Request Reply State handleCast Inc (State n) = do println "Cast: Inc" noReply $ State (n+1) handleCast Dec (State n) = do println "Cast: Dec" noReply $ State (n-1) handleCast _ st = noReply st
GenStatem Behaviour 抽象了對於 事件驅動的有限狀態機(Event-driven Finite State Machine) 中通用的操做。對於該類型的狀態機來講,它以觸發狀態轉換的事件做爲輸入,而在狀態轉換過程當中執行的動做做爲輸出,並獲得新的狀態。其模型以下:
State(S) x Event(E) -> Actions(A), State(S')
與 Erlang 中的實現相似,Hamler 使用 GenStatem 類型類對此狀態機的通用操做進行封裝。在 GenStatem
中僅提供一個事件處理的回調函數。其聲明以下:
class GenStatem e s d | e -> s, s -> d, d -> e where handleEvent :: HandleEvent e s d
CodeLock FSM Example
module Demo.FSM.CodeLock ( name , start , push , stop ) where import Prelude import Control.Behaviour.GenStatem ( class GenStatem , Action(..) , EventType(..) , Init , OnEvent , initOk , handleWith , unhandled ) import Control.Behaviour.GenStatem as FSM data Event = Button Integer | Lock data State = Locked | Opened data Data = Data { code :: [Integer] , length :: Integer , buttons :: [Integer] } instance Eq State where eq Locked Locked = true eq Opened Opened = true eq _ _ = false instance GenStatem Event State Data where handleEvent = handleWith [(Locked, locked), (Opened, opened)] name :: Atom name = :code_lock start :: [Integer] -> Process Pid start code = FSM.startLinkWith name (init code) push :: Integer -> Process () push n = FSM.cast name (Button n) stop :: Process () stop = FSM.stop name init :: [Integer] -> Init Event State Data init code = initOk Locked d where d = Data $ { code = reverse code , length = length code , buttons = [] } locked :: OnEvent Event State Data locked Cast (Button n) (Data d) = let buttons = take d.length [n|d.buttons] in if buttons == d.code then let actions = [StateTimeout 1000 Lock] in FSM.nextWith Opened (Data d{buttons = []}) actions else FSM.keep (Data d{buttons = buttons}) locked t e d = unhandled t e Locked d opened :: OnEvent Event State Data opened Cast (Button _) d = FSM.keep d opened Timeout Lock d = do println "Timeout Lock" FSM.next Locked d opened t e d = unhandled t e Opened d
Supervisor Behaviour 抽象了進程間容錯的通用操做,它做爲一個特殊的進程,以 監督者(Supervisor) 的角色管理其子進程,並在出現異常時重啓相關的子進程,以提升系統的容錯能力。
在 Hamler 中,這類行爲被封裝爲 Supervisor 的類型類,並提供一個 init
回調函數來配置監督者的行爲和子進程列表。這裏的實現與 Erlang 中的 supervisor
是一致的。
監督者能夠監控上文提到的 GenServer
或 GenStatem
生成的進程,一樣也能夠監控另一個監督者。這便構成了 監控樹(Supervision Tree)。以下圖所示:
其中矩形表示一個監督者,圓表示一個工做者(它能夠是一個 GenServer,GenStatem 或其它任意的進程)。當有進程異常退出時,監督者會按回調函數中配置的方式進行重啓,例如:
one_for_one
:僅重啓異常退出的子進程。one_for_all
:重啓該監督者下全部的子進程。參考資料:Supervision Principles Erlang Supervisor Behaviour
A Supervisor Example
module Demo.Sup (start) where import Prelude import Demo.Event as Event import Demo.Server as Server import Demo.FSM.PushButton as FSM import Control.Behaviour.Supervisor ( Init , initOk , Strategy(..) , childSpec , startSupWith ) name :: Atom name = :sup start :: Process Pid start = startSupWith name init init :: Init init = initOk (OneForOne, 10, 100) [ childSpec "Demo.Event" Event.start , childSpec "Demo.Server" Server.start , childSpec "Demo.Statem" FSM.start ]
Hamler 函數編程語言從發起便是一個開源項目,項目託管在 GitHub: https://github.com/hamler-lang/ 。Hamler 目前由 EMQ - 杭州映雲科技有限公司 研發團隊主導開發,計劃在 2020 年末前發佈 0.5 版本用於 EMQ X 6.0 的開發。
EMQ - 杭州映雲科技有限公司致力於成爲全球領先的消息與流處理開源軟件企業,聚焦服務於新產業週期的 5G&IoT、邊緣計算(Edge)與雲計算(Cloud)市場。EMQ 研發團隊主要採用 Erlang、Haskell 等函數編程語言,開發高併發、高可靠、軟實時的大規模分佈式系統。
版權聲明: 本文爲 EMQ 原創,轉載請註明出處。原文連接:https://www.emqx.io/cn/news/hamler-0-2-otp-behaviours-with-type-classes