Hamler 0.2 正式發佈 - OTP Behaviours with Type Classes

咱們在這裏很高興地和你們分享 Hamler 0.2 版本發佈的消息!html

Hamler 是一門構建在 Erlang 虛擬機(VM)上的 Haskell 風格的強類型(Strongly-typed)編程語言,獨特意結合了編譯時的類型檢查推導,與對運行時高併發和軟實時能力的支持。git

image

Hamler 0.2 現已支持大部分 Erlang 的併發編程特性,包括基於 Actor Model 的 Message Passing Concurrency 和 OTP Behaviours。github

關於 Actor Model

1974年,卡爾-休伊特教授發表了論文《Actor model of computation》。文中,他闡述了 Actor 做爲一個計算實體,它會對收到的消息做出迴應,並能夠併發地進行如下操做:編程

  • 向其餘 Actor 發送有限數量的消息
  • 建立有限數量的新 Actor
  • 指定下一個收到的消息所要使用的行爲

隨着多核計算和大規模分佈式系統的興起,Actor 模型因其自然的併發性、並行性和分佈式變得愈來愈重要。安全

Process and Mailbox

Hamler/Erlang 中的 Actor 被定義爲一個進程,它的工做方式就像一個 OS 進程。每一個進程都有本身的內存,由一個 Mailbox、一個 Heap、一個 Stack 和一個包含進程信息的 Process Control Block(PCB) 組成。服務器

image

Erlang 中的進程是很是輕量的,咱們能夠在一個正在運行的 Erlang 虛擬機上快速建立數百萬個進程。併發

Message Passing Concurrency

"Message passing concurrency(MPS)是兩個或多個進程之間沒有共享資源狀況下的併發,它們經過僅傳遞消息進行通訊。" Actor Model 就是 MPS 模型的一個實現。編程語言

參考資料:分佈式

MessagePassingConcurrency函數

AlanKayOnMessaging

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"

OTP Behaviours

Hamler 採用類型類(TypeClass)實現 OTP Behaviour。

TypeClass 定義了具備相似 operation 的一組類型。在咱們的實現中,使用 typeclass 來對不一樣 OTP Behaviour 的類型進行區分。經過爲每一個 Behavour 定義一個 typeclass 的方式,咱們對這些 Behaviour 作了某種程度上的抽象,並在必定程度上增長了類型約束。

GenServer Behaviour

Generic Server Behaviour 是對 客戶端-服務器 關係模型中服務器的抽象。如圖所示,在該模型的服務器側,全部的通用操做均可以被封裝成爲一個模塊。與 Erlang 同樣,Hamler 將其封裝爲 GenServer 的模塊。不一樣的是在 Hamler 中 GenServer 由類型類進行定義,它全部的回調函數和參數都必須受到類型約束,它在具有 Erlang 的 gen_server 特性的同時,也保證了類型的安全。以 handleCallhandleCast 爲例:

參考資料 Erlang gen_server Behaviour

image

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

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 Behaviour 抽象了進程間容錯的通用操做,它做爲一個特殊的進程,以 監督者(Supervisor) 的角色管理其子進程,並在出現異常時重啓相關的子進程,以提升系統的容錯能力。

在 Hamler 中,這類行爲被封裝爲 Supervisor 的類型類,並提供一個 init 回調函數來配置監督者的行爲和子進程列表。這裏的實現與 Erlang 中的 supervisor 是一致的。

Supervision Tree

監督者能夠監控上文提到的 GenServerGenStatem 生成的進程,一樣也能夠監控另一個監督者。這便構成了 監控樹(Supervision Tree)。以下圖所示:

image

其中矩形表示一個監督者,圓表示一個工做者(它能夠是一個 GenServer,GenStatem 或其它任意的進程)。當有進程異常退出時,監督者會按回調函數中配置的方式進行重啓,例如:

  • '1' 表示 one_for_one:僅重啓異常退出的子進程。
  • 'a' 表示 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 編程語言社區

Hamler 函數編程語言從發起便是一個開源項目,項目託管在 GitHub: https://github.com/hamler-lang/Hamler 目前由 EMQ - 杭州映雲科技有限公司 研發團隊主導開發,計劃在 2020 年末前發佈 0.5 版本用於 EMQ X 6.0 的開發。

EMQ 公司介紹

EMQ - 杭州映雲科技有限公司致力於成爲全球領先的消息與流處理開源軟件企業,聚焦服務於新產業週期的 5G&IoT、邊緣計算(Edge)與雲計算(Cloud)市場。EMQ 研發團隊主要採用 Erlang、Haskell 等函數編程語言,開發高併發、高可靠、軟實時的大規模分佈式系統。

版權聲明: 本文爲 EMQ 原創,轉載請註明出處。

原文連接:https://www.emqx.io/cn/news/hamler-0-2-otp-behaviours-with-type-classes

相關文章
相關標籤/搜索