我們在這裏很高興地和大家分享 Hamler 0.2 版本發佈的訊息!
Hamler 是一門構建在 Erlang 虛擬機器(VM)上的 Haskell 風格的強型別(Strongly-typed)程式語言,獨特地結合了編譯時的型別檢查推導,與對執行時高併發和軟實時能力的支援。
Hamler 0.2 現已支援大部分 Erlang 的併發程式設計特性,包括基於 Actor Model 的 Message Passing Concurrency 和 OTP Behaviours。
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