关于物联网:Hamler-02-正式发布-OTP-Behaviours-with-Type-Classes

咱们在这里很快乐地和大家分享 Hamler 0.2 版本公布的音讯!

Hamler 是一门构建在 Erlang 虚拟机(VM)上的 Haskell 格调的强类型(Strongly-typed)编程语言,独特地联合了编译时的类型查看推导,与对运行时高并发和软实时能力的反对。

Hamler 0.2 现已反对大部分 Erlang 的并发编程个性,包含基于 Actor Model 的 Message Passing Concurrency 和 OTP Behaviours。

对于 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) 组成。

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

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)。如下图所示:

其中矩形示意一个监督者,圆示意一个工作者(它能够是一个 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

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理