typed-session 0.1.0.0 → 0.1.1.0
raw patch · 17 files changed
+1053/−820 lines, 17 filesdep +template-haskelldep +typed-session-state-algorithmPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependencies added: template-haskell, typed-session-state-algorithm
API changes (from Hackage documentation)
- TypedProtocol.Codec: Channel :: (bytes -> m ()) -> m (Maybe bytes) -> Channel (m :: Type -> Type) bytes
- TypedProtocol.Codec: CodecFailure :: String -> CodecFailure
- TypedProtocol.Codec: CodecFailureOutOfInput :: CodecFailure
- TypedProtocol.Codec: Decode :: DecodeStep bytes failure (AnyMsg role' ps) -> Decode role' ps failure bytes
- TypedProtocol.Codec: DecodeDone :: a -> Maybe bytes -> DecodeStep bytes failure a
- TypedProtocol.Codec: DecodeFail :: failure -> DecodeStep bytes failure a
- TypedProtocol.Codec: DecodePartial :: (Maybe bytes -> DecodeStep bytes failure a) -> DecodeStep bytes failure a
- TypedProtocol.Codec: Encode :: (forall (send :: role') (recv :: role') (st :: ps) (st' :: ps) (st'' :: ps). () => Msg role' ps st '(send, st') '(recv, st'') -> bytes) -> Encode role' ps bytes
- TypedProtocol.Codec: [decode] :: Decode role' ps failure bytes -> DecodeStep bytes failure (AnyMsg role' ps)
- TypedProtocol.Codec: [encode] :: Encode role' ps bytes -> forall (send :: role') (recv :: role') (st :: ps) (st' :: ps) (st'' :: ps). () => Msg role' ps st '(send, st') '(recv, st'') -> bytes
- TypedProtocol.Codec: [recv] :: Channel (m :: Type -> Type) bytes -> m (Maybe bytes)
- TypedProtocol.Codec: [send] :: Channel (m :: Type -> Type) bytes -> bytes -> m ()
- TypedProtocol.Codec: data Channel (m :: Type -> Type) bytes
- TypedProtocol.Codec: data CodecFailure
- TypedProtocol.Codec: data DecodeStep bytes failure a
- TypedProtocol.Codec: instance GHC.Classes.Eq TypedProtocol.Codec.CodecFailure
- TypedProtocol.Codec: instance GHC.Internal.Exception.Type.Exception TypedProtocol.Codec.CodecFailure
- TypedProtocol.Codec: instance GHC.Internal.Show.Show TypedProtocol.Codec.CodecFailure
- TypedProtocol.Codec: newtype Decode role' ps failure bytes
- TypedProtocol.Codec: newtype Encode role' ps bytes
- TypedProtocol.Codec: runDecoderWithChannel :: Monad m => Channel m bytes -> Maybe bytes -> DecodeStep bytes failure a -> m (Either failure (a, Maybe bytes))
- TypedProtocol.Core: [AnyMsg] :: forall role' (recv :: role') ps (st :: ps) (send :: role') (st' :: ps) (st'' :: ps). (SingI recv, SingI st, SingToInt role', SingToInt ps) => Msg role' ps st '(send, st') '(recv, st'') -> AnyMsg role' ps
- TypedProtocol.Core: [Await] :: forall role' (r :: role') ps (st :: ps) (m :: Type -> Type) (ia :: ps -> Type). (SingI r, SingI st, SingToInt ps) => (Recv role' ps r st ~> Peer role' ps r m ia) -> Peer role' ps r m ia st
- TypedProtocol.Core: [IReturn] :: forall ps (ia :: ps -> Type) (st :: ps) role' (r :: role') (m :: Type -> Type). ia st -> Peer role' ps r m ia st
- TypedProtocol.Core: [LiftM] :: forall (m :: Type -> Type) role' ps (r :: role') (ia :: ps -> Type) (st' :: ps) (st :: ps). m (Peer role' ps r m ia st') -> Peer role' ps r m ia st
- TypedProtocol.Core: [Recv] :: forall role' ps (from :: ps) (send :: role') (sps :: ps) (recv :: role') (to :: ps). Msg role' ps from '(send, sps) '(recv, to) -> Recv role' ps recv from to
- TypedProtocol.Core: [Yield] :: forall role' (recv :: role') ps (st :: ps) (r :: role') (sps :: ps) (rps :: ps) (m :: Type -> Type) (ia :: ps -> Type). (SingI recv, SingI st, SingToInt ps) => Msg role' ps st '(r, sps) '(recv, rps) -> Peer role' ps r m ia sps -> Peer role' ps r m ia st
- TypedProtocol.Core: await :: forall (m :: Type -> Type) role' (recv :: role') ps (from :: ps). (Functor m, SingI recv, SingI from, SingToInt ps) => Peer role' ps recv m (Recv role' ps recv from) from
- TypedProtocol.Core: class (SingToInt role', SingToInt ps) => Protocol role' ps where {
- TypedProtocol.Core: class SingToInt s
- TypedProtocol.Core: data AnyMsg role' ps
- TypedProtocol.Core: data Msg role' ps (from :: ps) (sendAndSt :: (role', ps)) (recvAndSt :: (role', ps));
- TypedProtocol.Core: data Peer role' ps (r :: role') (m :: Type -> Type) (ia :: ps -> Type) (st :: ps)
- TypedProtocol.Core: data Recv role'1 ps1 (recv :: role') (from :: ps) (to :: ps)
- TypedProtocol.Core: instance forall (m :: * -> *) role' ps (r :: role'). GHC.Internal.Base.Functor m => Data.IFunctor.IFunctor (TypedProtocol.Core.Peer role' ps r m)
- TypedProtocol.Core: instance forall (m :: * -> *) role' ps (r :: role'). GHC.Internal.Base.Functor m => Data.IFunctor.IMonad (TypedProtocol.Core.Peer role' ps r m)
- TypedProtocol.Core: instance forall (m :: * -> *) role' ps (r :: role'). GHC.Internal.Base.Functor m => Data.IFunctor.IMonadFail (TypedProtocol.Core.Peer role' ps r m)
- TypedProtocol.Core: liftm :: forall m a role' ps (r :: role') (ts :: ps). Functor m => m a -> Peer role' ps r m (At a ts) ts
- TypedProtocol.Core: msgFromStSing :: forall role' ps (st :: ps) (send :: role') (recv :: role') (st' :: ps) (st'' :: ps). (SingI recv, SingI st) => Msg role' ps st '(send, st') '(recv, st'') -> Sing st
- TypedProtocol.Core: singToInt :: forall (r :: s). SingToInt s => Sing r -> Int
- TypedProtocol.Core: type Done (sr :: role') :: ps;
- TypedProtocol.Core: type MsgCache role' ps = IntMap AnyMsg role' ps
- TypedProtocol.Core: yield :: forall (m :: Type -> Type) role' (recv :: role') ps (from :: ps) (send :: role') (sps :: ps) (rps :: ps). (Functor m, SingI recv, SingI from, SingToInt ps) => Msg role' ps from '(send, sps) '(recv, rps) -> Peer role' ps send m (At () sps) from
- TypedProtocol.Core: }
- TypedProtocol.Driver: Driver :: (forall (send :: role') (recv :: role') (st :: ps) (st' :: ps) (st'' :: ps). (SingI recv, SingI st, SingToInt ps, SingToInt role') => Sing recv -> Msg role' ps st '(send, st') '(recv, st'') -> m ()) -> (forall (st' :: ps). SingToInt ps => Sing st' -> m (AnyMsg role' ps)) -> Driver role' ps (m :: Type -> Type)
- TypedProtocol.Driver: [TraceRecvMsg] :: forall role' ps. AnyMsg role' ps -> TraceSendRecv role' ps
- TypedProtocol.Driver: [TraceSendMsg] :: forall role' ps. AnyMsg role' ps -> TraceSendRecv role' ps
- TypedProtocol.Driver: [recvMsg] :: Driver role' ps (m :: Type -> Type) -> forall (st' :: ps). SingToInt ps => Sing st' -> m (AnyMsg role' ps)
- TypedProtocol.Driver: [sendMsg] :: Driver role' ps (m :: Type -> Type) -> forall (send :: role') (recv :: role') (st :: ps) (st' :: ps) (st'' :: ps). (SingI recv, SingI st, SingToInt ps, SingToInt role') => Sing recv -> Msg role' ps st '(send, st') '(recv, st'') -> m ()
- TypedProtocol.Driver: data Driver role' ps (m :: Type -> Type)
- TypedProtocol.Driver: data TraceSendRecv role' ps
- TypedProtocol.Driver: decodeLoop :: (Exception failure, MonadSTM n, MonadThrow n) => Tracer role' ps n -> Maybe bytes -> Decode role' ps failure bytes -> Channel n bytes -> TVar n (MsgCache role' ps) -> n ()
- TypedProtocol.Driver: driverSimple :: forall role' ps bytes m n. (Monad m, Monad n, MonadSTM n) => Tracer role' ps n -> Encode role' ps bytes -> SendMap role' n bytes -> TVar n (MsgCache role' ps) -> (forall a. () => n a -> m a) -> Driver role' ps m
- TypedProtocol.Driver: instance GHC.Internal.Show.Show (TypedProtocol.Core.AnyMsg role' ps) => GHC.Internal.Show.Show (TypedProtocol.Driver.TraceSendRecv role' ps)
- TypedProtocol.Driver: nullTracer :: Monad m => a -> m ()
- TypedProtocol.Driver: runPeerWithDriver :: forall role' ps (r :: role') (st :: ps) m a. (Monad m, SingToInt role') => Driver role' ps m -> Peer role' ps r m (At a (Done r :: ps)) st -> m a
- TypedProtocol.Driver: type SendMap (role' :: k) (m :: Type -> Type) bytes = IntMap bytes -> m ()
- TypedProtocol.Driver: type Tracer role' ps (m :: Type -> Type) = TraceSendRecv role' ps -> m ()
+ TypedSession.Codec: Channel :: (bytes -> m ()) -> m (Maybe bytes) -> Channel (m :: Type -> Type) bytes
+ TypedSession.Codec: CodecFailure :: String -> CodecFailure
+ TypedSession.Codec: CodecFailureOutOfInput :: CodecFailure
+ TypedSession.Codec: Decode :: DecodeStep bytes failure (AnyMsg role' ps) -> Decode role' ps failure bytes
+ TypedSession.Codec: DecodeDone :: a -> Maybe bytes -> DecodeStep bytes failure a
+ TypedSession.Codec: DecodeFail :: failure -> DecodeStep bytes failure a
+ TypedSession.Codec: DecodePartial :: (Maybe bytes -> DecodeStep bytes failure a) -> DecodeStep bytes failure a
+ TypedSession.Codec: Encode :: (forall (send :: role') (recv :: role') (st :: ps) (st' :: ps) (st'' :: ps). () => Msg role' ps st '(send, st') '(recv, st'') -> bytes) -> Encode role' ps bytes
+ TypedSession.Codec: [decode] :: Decode role' ps failure bytes -> DecodeStep bytes failure (AnyMsg role' ps)
+ TypedSession.Codec: [encode] :: Encode role' ps bytes -> forall (send :: role') (recv :: role') (st :: ps) (st' :: ps) (st'' :: ps). () => Msg role' ps st '(send, st') '(recv, st'') -> bytes
+ TypedSession.Codec: [recv] :: Channel (m :: Type -> Type) bytes -> m (Maybe bytes)
+ TypedSession.Codec: [send] :: Channel (m :: Type -> Type) bytes -> bytes -> m ()
+ TypedSession.Codec: data Channel (m :: Type -> Type) bytes
+ TypedSession.Codec: data CodecFailure
+ TypedSession.Codec: data DecodeStep bytes failure a
+ TypedSession.Codec: instance GHC.Classes.Eq TypedSession.Codec.CodecFailure
+ TypedSession.Codec: instance GHC.Internal.Exception.Type.Exception TypedSession.Codec.CodecFailure
+ TypedSession.Codec: instance GHC.Internal.Show.Show TypedSession.Codec.CodecFailure
+ TypedSession.Codec: newtype Decode role' ps failure bytes
+ TypedSession.Codec: newtype Encode role' ps bytes
+ TypedSession.Codec: runDecoderWithChannel :: Monad m => Channel m bytes -> Maybe bytes -> DecodeStep bytes failure a -> m (Either failure (a, Maybe bytes))
+ TypedSession.Core: [AnyMsg] :: forall role' (recv :: role') ps (st :: ps) (send :: role') (st' :: ps) (st'' :: ps). (SingI recv, SingI st, SingToInt role', SingToInt ps) => Msg role' ps st '(send, st') '(recv, st'') -> AnyMsg role' ps
+ TypedSession.Core: [Await] :: forall role' (r :: role') ps (st :: ps) (m :: Type -> Type) (ia :: ps -> Type). (SingI r, SingI st, SingToInt ps) => (Recv role' ps r st ~> Peer role' ps r m ia) -> Peer role' ps r m ia st
+ TypedSession.Core: [IReturn] :: forall ps (ia :: ps -> Type) (st :: ps) role' (r :: role') (m :: Type -> Type). ia st -> Peer role' ps r m ia st
+ TypedSession.Core: [LiftM] :: forall (m :: Type -> Type) role' ps (r :: role') (ia :: ps -> Type) (st' :: ps) (st :: ps). m (Peer role' ps r m ia st') -> Peer role' ps r m ia st
+ TypedSession.Core: [Recv] :: forall role' ps (from :: ps) (send :: role') (sps :: ps) (recv :: role') (to :: ps). Msg role' ps from '(send, sps) '(recv, to) -> Recv role' ps recv from to
+ TypedSession.Core: [Yield] :: forall role' (recv :: role') ps (st :: ps) (r :: role') (sps :: ps) (rps :: ps) (m :: Type -> Type) (ia :: ps -> Type). (SingI recv, SingI st, SingToInt ps) => Msg role' ps st '(r, sps) '(recv, rps) -> Peer role' ps r m ia sps -> Peer role' ps r m ia st
+ TypedSession.Core: await :: forall (m :: Type -> Type) role' (recv :: role') ps (from :: ps). (Functor m, SingI recv, SingI from, SingToInt ps) => Peer role' ps recv m (Recv role' ps recv from) from
+ TypedSession.Core: class (SingToInt role', SingToInt ps) => Protocol role' ps where {
+ TypedSession.Core: class SingToInt s
+ TypedSession.Core: data AnyMsg role' ps
+ TypedSession.Core: data Msg role' ps (from :: ps) (sendAndSt :: (role', ps)) (recvAndSt :: (role', ps));
+ TypedSession.Core: data Peer role' ps (r :: role') (m :: Type -> Type) (ia :: ps -> Type) (st :: ps)
+ TypedSession.Core: data Recv role'1 ps1 (recv :: role') (from :: ps) (to :: ps)
+ TypedSession.Core: instance forall (m :: * -> *) role' ps (r :: role'). GHC.Internal.Base.Functor m => Data.IFunctor.IFunctor (TypedSession.Core.Peer role' ps r m)
+ TypedSession.Core: instance forall (m :: * -> *) role' ps (r :: role'). GHC.Internal.Base.Functor m => Data.IFunctor.IMonad (TypedSession.Core.Peer role' ps r m)
+ TypedSession.Core: instance forall (m :: * -> *) role' ps (r :: role'). GHC.Internal.Base.Functor m => Data.IFunctor.IMonadFail (TypedSession.Core.Peer role' ps r m)
+ TypedSession.Core: liftm :: forall m a role' ps (r :: role') (ts :: ps). Functor m => m a -> Peer role' ps r m (At a ts) ts
+ TypedSession.Core: msgFromStSing :: forall role' ps (st :: ps) (send :: role') (recv :: role') (st' :: ps) (st'' :: ps). (SingI recv, SingI st) => Msg role' ps st '(send, st') '(recv, st'') -> Sing st
+ TypedSession.Core: singToInt :: forall (r :: s). SingToInt s => Sing r -> Int
+ TypedSession.Core: type Done (sr :: role') :: ps;
+ TypedSession.Core: type MsgCache role' ps = IntMap AnyMsg role' ps
+ TypedSession.Core: yield :: forall (m :: Type -> Type) role' (recv :: role') ps (from :: ps) (send :: role') (sps :: ps) (rps :: ps). (Functor m, SingI recv, SingI from, SingToInt ps) => Msg role' ps from '(send, sps) '(recv, rps) -> Peer role' ps send m (At () sps) from
+ TypedSession.Core: }
+ TypedSession.Driver: Driver :: (forall (send :: role') (recv :: role') (st :: ps) (st' :: ps) (st'' :: ps). (SingI recv, SingI st, SingToInt ps, SingToInt role') => Sing recv -> Msg role' ps st '(send, st') '(recv, st'') -> m ()) -> (forall (st' :: ps). SingToInt ps => Sing st' -> m (AnyMsg role' ps)) -> Driver role' ps (m :: Type -> Type)
+ TypedSession.Driver: [TraceRecvMsg] :: forall role' ps. AnyMsg role' ps -> TraceSendRecv role' ps
+ TypedSession.Driver: [TraceSendMsg] :: forall role' ps. AnyMsg role' ps -> TraceSendRecv role' ps
+ TypedSession.Driver: [recvMsg] :: Driver role' ps (m :: Type -> Type) -> forall (st' :: ps). SingToInt ps => Sing st' -> m (AnyMsg role' ps)
+ TypedSession.Driver: [sendMsg] :: Driver role' ps (m :: Type -> Type) -> forall (send :: role') (recv :: role') (st :: ps) (st' :: ps) (st'' :: ps). (SingI recv, SingI st, SingToInt ps, SingToInt role') => Sing recv -> Msg role' ps st '(send, st') '(recv, st'') -> m ()
+ TypedSession.Driver: data Driver role' ps (m :: Type -> Type)
+ TypedSession.Driver: data TraceSendRecv role' ps
+ TypedSession.Driver: decodeLoop :: (Exception failure, MonadSTM n, MonadThrow n) => Tracer role' ps n -> Maybe bytes -> Decode role' ps failure bytes -> Channel n bytes -> TVar n (MsgCache role' ps) -> n ()
+ TypedSession.Driver: driverSimple :: forall role' ps bytes m n. (Monad m, Monad n, MonadSTM n) => Tracer role' ps n -> Encode role' ps bytes -> SendMap role' n bytes -> TVar n (MsgCache role' ps) -> (forall a. () => n a -> m a) -> Driver role' ps m
+ TypedSession.Driver: instance GHC.Internal.Show.Show (TypedSession.Core.AnyMsg role' ps) => GHC.Internal.Show.Show (TypedSession.Driver.TraceSendRecv role' ps)
+ TypedSession.Driver: nullTracer :: Monad m => a -> m ()
+ TypedSession.Driver: runPeerWithDriver :: forall role' ps (r :: role') (st :: ps) m a. (Monad m, SingToInt role') => Driver role' ps m -> Peer role' ps r m (At a (Done r :: ps)) st -> m a
+ TypedSession.Driver: type SendMap (role' :: k) (m :: Type -> Type) bytes = IntMap bytes -> m ()
+ TypedSession.Driver: type Tracer role' ps (m :: Type -> Type) = TraceSendRecv role' ps -> m ()
+ TypedSession.TH: addS :: Name -> Name
+ TypedSession.TH: protDecsAndMsgDecs :: (Show r, Show bst) => String -> Name -> Name -> PipleResult r bst -> Q [Dec]
+ TypedSession.TH: protocol :: (Enum r, Bounded r, Show r, Enum bst, Bounded bst, Show bst, Ord r) => String -> Name -> Name -> QuasiQuoter
+ TypedSession.TH: roleDecs :: Name -> Q [Dec]
Files
- CHANGELOG.md +3/−0
- src/TypedProtocol/Codec.hs +0/−72
- src/TypedProtocol/Core.hs +0/−254
- src/TypedProtocol/Driver.hs +0/−216
- src/TypedSession/Codec.hs +72/−0
- src/TypedSession/Core.hs +254/−0
- src/TypedSession/Driver.hs +217/−0
- src/TypedSession/TH.hs +293/−0
- test/Book.hs +3/−3
- test/Book1.hs +3/−3
- test/Book2.hs +3/−3
- test/Book3/Main.hs +9/−8
- test/Book3/Peer.hs +25/−25
- test/Book3/Protocol.hs +143/−0
- test/Book3/Type.hs +15/−228
- test/PingPong.hs +3/−3
- typed-session.cabal +10/−5
CHANGELOG.md view
@@ -1,3 +1,6 @@ # Revision history for typed-session +## 0.1.1.0+* add protocol TH+ ## 0.1.0.0-- 2024-8-6
− src/TypedProtocol/Codec.hs
@@ -1,72 +0,0 @@--- This part of the code comes from typed-protocols, I modified a few things.-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE GADTs #-}-{-# LANGUAGE NamedFieldPuns #-}-{-# LANGUAGE PolyKinds #-}-{-# LANGUAGE QuantifiedConstraints #-}-{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE UndecidableInstances #-}--module TypedProtocol.Codec where--import Control.Exception (Exception)-import TypedProtocol.Core--{- |-Function to encode Msg into bytes.--}-newtype Encode role' ps bytes = Encode- { encode- :: forall (send :: role') (recv :: role') (st :: ps) (st' :: ps) (st'' :: ps)- . Msg role' ps st '(send, st') '(recv, st'')- -> bytes- }--{- |-Incremental decoding function.--}-newtype Decode role' ps failure bytes = Decode- { decode :: DecodeStep bytes failure (AnyMsg role' ps)- }--{- |-Generic incremental decoder constructor, you need to convert specific incremental decoders to it.--}-data DecodeStep bytes failure a- = DecodePartial (Maybe bytes -> (DecodeStep bytes failure a))- | DecodeDone a (Maybe bytes)- | DecodeFail failure--data CodecFailure- = CodecFailureOutOfInput- | CodecFailure String- deriving (Eq, Show)--instance Exception CodecFailure--{- |-Bottom functions for sending and receiving bytes.--}-data Channel m bytes = Channel- { send :: bytes -> m ()- , recv :: m (Maybe bytes)- }--{- |-Generic incremental decoding function.--}-runDecoderWithChannel- :: (Monad m)- => Channel m bytes- -> Maybe bytes- -> DecodeStep bytes failure a- -> m (Either failure (a, Maybe bytes))-runDecoderWithChannel Channel{recv} = go- where- go _ (DecodeDone x trailing) = return (Right (x, trailing))- go _ (DecodeFail failure) = return (Left failure)- go Nothing (DecodePartial k) = recv >>= pure . k >>= go Nothing- go (Just trailing) (DecodePartial k) = (pure . k) (Just trailing) >>= go Nothing
− src/TypedProtocol/Core.hs
@@ -1,254 +0,0 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE GADTs #-}-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE PolyKinds #-}-{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeApplications #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE TypeOperators #-}--module TypedProtocol.Core where--import Data.IFunctor-import Data.IntMap (IntMap)-import Data.Kind--{- |--typed-session is a communication framework.-The messages received from the outside will be placed in MsgCache.-When interpreting the Peer, (Sing (r :: s)) will be generated according to the Peer's status.-SingToInt can convert Sing (r :: s) to Int.-Depending on this Int value, the required Msg is finally found from MsgCache.--In the process of multi-role communication, a message cache structure like MsgCache is needed.--Consider the following scenario--@-s1 s1 s2 Initial state--------------a -> b a sends message MsgA to b--------------s3 s2 s2 State after sending-------------- b <- c c sends message MsgC to b--------------s3 s4 s5 State after sending-@--For b, due to the influence of network transmission, it cannot guarantee that it can receive MsgA first and then MsgC.-If it receives MsgC first, it will directly put it in MsgCache and continue to wait until-MsgA arrives, then it will start to process MsgA first and then MsgC.--In general, dataToTag# is used directly here.--Example:--@-instance SingToInt Role where- singToInt x = I# (dataToTag# x)--instance SingToInt PingPong where- singToInt x = I# (dataToTag# x)-@--}-class SingToInt s where- singToInt :: Sing (r :: s) -> Int--{- |--Describes the type class of Msg. The core of typed-session.--@-type Done (sr :: role') :: ps-@--Describe the state of each role when it terminates.--@-data Msg role' ps (from :: ps) (sendAndSt :: (role', ps)) (recvAndSt :: (role', ps))-@-* role': the type of the role.-* ps: the type of the state machine.-* from: when sending a message, the sender is in this state,-where the receiver may be in this state, or a more generalized state related to this state.-For example, the sender is in state (S1 [True]), and the receiver is in state (S1 s).-* sendAndSt: the role that sends the message and the state of the role after sending the message.-* recvAndSt: the role that receives the message and the state of the role after receiving the message.--There are two principles to follow when designing the state of Msg:--1. When sending a message, the sender and receiver must be in the same state. Here the receiver may be in a more generalized state related to the state.-For example, the sender is in state (S1 [True]), and the receiver is in state (S1 s).--2. The same state can only be used for the same pair of receiver and sender.--For example, in the following example, state s1 is used for both (a -> b) and (b -> c), which is wrong.--@-s1 s1 s1-a -> b-s2 s1 s1- b -> c-s2 s4 s5-@--}-class (SingToInt role', SingToInt ps) => Protocol role' ps where- type Done (sr :: role') :: ps- data Msg role' ps (from :: ps) (sendAndSt :: (role', ps)) (recvAndSt :: (role', ps))--{- |-Package Msg and extract the required type.--@-Msg role' ps from '(send, sps) '(recv, rps)-Recv role' ps recv from rps-@--}-data Recv role' ps recv from to where- Recv- :: Msg role' ps from '(send, sps) '(recv, rps)- -> Recv role' ps recv from rps--{- |-Messages received from the outside are placed in MsgCache. When interpreting-Peer will use the Msg in MsgCache.--}-type MsgCache role' ps = IntMap (AnyMsg role' ps)--{- |-Packaging of Msg, shielding part of the type information, mainly used for serialization.--}-data AnyMsg role' ps where- AnyMsg- :: ( SingI recv- , SingI st- , SingToInt role'- , SingToInt ps- )- => Msg role' ps st '(send, st') '(recv, st'')- -> AnyMsg role' ps--msgFromStSing- :: forall role' ps st send recv st' st''- . (SingI recv, SingI st)- => Msg role' ps st '(send, st') '(recv, st'')- -> Sing st-msgFromStSing _ = sing @st--{- |-Core Ast, all we do is build this Ast and then interpret it.--@-IReturn :: ia st -> Peer role' ps r m ia st-@-IReturn indicates the termination of the continuation.--@-LiftM :: m (Peer role' ps r m ia st') -> Peer role' ps r m ia st-@--Liftm can transform state st to any state st'.-It looks a bit strange, as if it is a constructor that is not constrained by the Msg type.-Be careful when using it, it is a type breakpoint.-But some state transition functions need it, which can make the code more flexible.-Be very careful when using it!--@-Yield- :: ( SingI recv- , SingI from- , SingToInt ps- )- => Msg role' ps from '(send, sps) '(recv, rps)- -> Peer role' ps send m ia sps- -> Peer role' ps send m ia from-@-Yield represents sending a message. Note that the Peer status changes from `from` to `sps`.--@-Await- :: ( SingI recv- , SingI from- , SingToInt ps- )- => (Recv role' ps recv from ~> Peer role' ps recv m ia)- -> Peer role' ps recv m ia from-@--Await represents receiving messages.-Different messages will lead to different states.-The state is passed to the next behavior through (~>).--}-data Peer role' ps (r :: role') (m :: Type -> Type) (ia :: ps -> Type) (st :: ps) where- IReturn :: ia st -> Peer role' ps r m ia st- LiftM :: m (Peer role' ps r m ia st') -> Peer role' ps r m ia st- Yield- :: ( SingI recv- , SingI from- , SingToInt ps- )- => Msg role' ps from '(send, sps) '(recv, rps)- -> Peer role' ps send m ia sps- -> Peer role' ps send m ia from- Await- :: ( SingI recv- , SingI from- , SingToInt ps- )- => (Recv role' ps recv from ~> Peer role' ps recv m ia)- -> Peer role' ps recv m ia from--instance (Functor m) => IMonadFail (Peer role' ps r m) where- fail = error--instance (Functor m) => IFunctor (Peer role' ps r m) where- imap f = \case- IReturn ia -> IReturn (f ia)- LiftM f' -> LiftM (fmap (imap f) f')- Yield ms cont -> Yield ms (imap f cont)- Await cont -> Await (imap f . cont)--instance (Functor m) => IMonad (Peer role' ps r m) where- ireturn = IReturn- ibind f = \case- IReturn ia -> (f ia)- LiftM f' -> LiftM (fmap (ibind f) f')- Yield ms cont -> Yield ms (ibind f cont)- Await cont -> Await (ibind f . cont)--{- |-Send a message, the Peer status changes from `from` to `sps`.--}-yield- :: ( Functor m- , SingI recv- , SingI from- , SingToInt ps- )- => Msg role' ps from '(send, sps) '(recv, rps)- -> Peer role' ps send m (At () sps) from-yield msg = Yield msg (returnAt ())--{- |-Receiving Messages.--}-await- :: ( Functor m- , SingI recv- , SingI from- , SingToInt ps- )- => Peer role' ps recv m (Recv role' ps recv from) from-await = Await ireturn--{- |-Lift any m to Peer role' ps r m, which is an application of LiftM. -Note that the state of `ts` has not changed.--}-liftm :: (Functor m) => m a -> Peer role' ps r m (At a ts) ts-liftm m = LiftM (returnAt <$> m)
− src/TypedProtocol/Driver.hs
@@ -1,216 +0,0 @@--- This part of the code comes from typed-protocols, I modified a few things.-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE GADTs #-}-{-# LANGUAGE NamedFieldPuns #-}-{-# LANGUAGE PolyKinds #-}-{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeApplications #-}-{-# LANGUAGE TypeOperators #-}-{-# LANGUAGE UndecidableInstances #-}-{-# OPTIONS_GHC -Wno-unused-do-bind #-}--{- |-Schematic diagram of the communication structure of three roles through typed-session:---Some explanations for this diagram:--1. Roles are connected through channels, and there are many types of channels, such as channels established through TCP or channels established through TMVar.--2. Each role has a Peer thread, in which the Peer runs.--3. Each role has one or more decode threads, and the decoded Msgs are placed in the MsgCache.--4. SendMap aggregates the send functions of multiple Channels together.-When sending a message, the send function of the receiver is searched from SendMap.--}-module TypedProtocol.Driver where--import Control.Concurrent.Class.MonadSTM-import Control.Monad.Class.MonadThrow (MonadThrow, throwIO)-import Data.IFunctor (At (..), Sing, SingI (sing))-import qualified Data.IFunctor as I-import Data.IntMap (IntMap)-import qualified Data.IntMap as IntMap-import GHC.Exception (Exception)-import TypedProtocol.Codec-import TypedProtocol.Core-import Unsafe.Coerce (unsafeCoerce)--{- |-Contains two functions sendMsg, recvMsg.-runPeerWithDriver uses them to send and receive Msg.--}-data Driver role' ps m- = Driver- { sendMsg- :: forall (send :: role') (recv :: role') (st :: ps) (st' :: ps) (st'' :: ps)- . ( SingI recv- , SingI st- , SingToInt ps- , SingToInt role'- )- => Sing recv- -> Msg role' ps st '(send, st') '(recv, st'')- -> m ()- , recvMsg- :: forall (st' :: ps)- . (SingToInt ps)- => Sing st'- -> m (AnyMsg role' ps)- }--{- |-Interpret Peer.--}-runPeerWithDriver- :: forall role' ps (r :: role') (st :: ps) m a- . ( Monad m- , (SingToInt role')- )- => Driver role' ps m- -> Peer role' ps r m (At a (Done r)) st- -> m a-runPeerWithDriver Driver{sendMsg, recvMsg} =- go- where- go- :: forall st'- . Peer role' ps r m (At a (Done r)) st'- -> m a- go (IReturn (At a)) = pure a- go (LiftM k) = k >>= go- go (Yield (msg :: Msg role' ps (st' :: ps) '(r, sps) '(recv :: role', rps)) k) = do- sendMsg (sing @recv) msg- go k- go (Await (k :: (Recv role' ps r st' I.~> Peer role' ps r m ia))) = do- AnyMsg msg <- recvMsg (sing @st')- go (k $ unsafeCoerce (Recv msg))--{- |-A wrapper around AnyMsg that represents sending and receiving Msg.--}-data TraceSendRecv role' ps where- TraceSendMsg :: AnyMsg role' ps -> TraceSendRecv role' ps- TraceRecvMsg :: AnyMsg role' ps -> TraceSendRecv role' ps--instance (Show (AnyMsg role' ps)) => Show (TraceSendRecv role' ps) where- show (TraceSendMsg msg) = "Send " ++ show msg- show (TraceRecvMsg msg) = "Recv " ++ show msg--{- |-Similar to the log function, used to print received or sent messages.--}-type Tracer role' ps m = TraceSendRecv role' ps -> m ()--{- |-The default trace function. It simply ignores everything.--}-nullTracer :: (Monad m) => a -> m ()-nullTracer _ = pure ()--{- |-SendMap aggregates the send functions of multiple Channels together.-When sending a message, the send function of the receiver is found from SendMap.--}-type SendMap role' m bytes = IntMap (bytes -> m ())--{- |--Build Driver through SendMap and MsgCache.-Here we need some help from other functions:--1. `Tracer role' ps n` is similar to the log function, used to print received or sent messages.-2. `Encode role' ps` bytes encoding function, converts Msg into bytes.-3. `forall a. n a -> m a` This is a bit complicated, I will explain it in detail below.--I see Peer as three layers:--1. `Peer` upper layer, meets the requirements of McBride Indexed Monad, uses do syntax construction, has semantic checks, and is interpreted to the second layer m through runPeerWithDriver.-2. `m` middle layer, describes the business requirements in this layer, and converts the received Msg into specific business actions.-3. `n` bottom layer, responsible for receiving and sending bytes. It can have multiple options such as IO or IOSim. Using IOSim can easily test the code.--}-driverSimple- :: forall role' ps bytes m n- . ( Monad m- , Monad n- , MonadSTM n- )- => Tracer role' ps n- -> Encode role' ps bytes- -> SendMap role' n bytes- -> TVar n (MsgCache role' ps)- -> (forall a. n a -> m a)- -> Driver role' ps m-driverSimple tracer Encode{encode} sendMap tvar liftFun =- Driver{sendMsg, recvMsg}- where- sendMsg- :: forall (send :: role') (recv :: role') (from :: ps) (st :: ps) (st1 :: ps)- . ( SingI recv- , SingI from- , SingToInt ps- , SingToInt role'- )- => Sing recv- -> Msg role' ps from '(send, st) '(recv, st1)- -> m ()- sendMsg role msg = liftFun $ do- case IntMap.lookup (singToInt role) sendMap of- Nothing -> error "np"- Just sendFun -> sendFun (encode msg)- tracer (TraceSendMsg (AnyMsg msg))-- recvMsg- :: forall (st' :: ps)- . (SingToInt ps)- => Sing st'- -> m (AnyMsg role' ps)- recvMsg sst' = do- let singInt = singToInt sst'- liftFun $ do- anyMsg <- atomically $ do- agencyMsg <- readTVar tvar- case IntMap.lookup singInt agencyMsg of- Nothing -> retry- Just v -> do- writeTVar tvar (IntMap.delete singInt agencyMsg)- pure v- tracer (TraceRecvMsg (anyMsg))- pure anyMsg--{- |-decode loop, usually in a separate thread.--The decoded Msg is placed in MsgCache.--@-data Msg role' ps (from :: ps) (sendAndSt :: (role', ps)) (recvAndSt :: (role', ps))-@-Note that when placing a new Msg in MsgCache, if a Msg with the same `from` already exists in MsgCache, the decoding process will be blocked,-until that Msg is consumed before placing the new Msg in MsgCache.--This usually happens when the efficiency of Msg generation is greater than the efficiency of consumption.--}-decodeLoop- :: (Exception failure, MonadSTM n, MonadThrow n)- => Tracer role' ps n- -> Maybe bytes- -> Decode role' ps failure bytes- -> Channel n bytes- -> TVar n (MsgCache role' ps)- -> n ()-decodeLoop tracer mbt d@Decode{decode} channel tvar = do- result <- runDecoderWithChannel channel mbt decode- case result of- Right (AnyMsg msg, mbt') -> do- let agencyInt = singToInt $ msgFromStSing msg- atomically $ do- agencyMsg <- readTVar tvar- case IntMap.lookup agencyInt agencyMsg of- Nothing -> writeTVar tvar (IntMap.insert agencyInt (AnyMsg msg) agencyMsg)- Just _v -> retry- decodeLoop tracer mbt' d channel tvar- Left failure -> throwIO failure
+ src/TypedSession/Codec.hs view
@@ -0,0 +1,72 @@+-- This part of the code comes from typed-protocols, I modified a few things.+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE QuantifiedConstraints #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-}++module TypedSession.Codec where++import Control.Exception (Exception)+import TypedSession.Core++{- |+Function to encode Msg into bytes.+-}+newtype Encode role' ps bytes = Encode+ { encode+ :: forall (send :: role') (recv :: role') (st :: ps) (st' :: ps) (st'' :: ps)+ . Msg role' ps st '(send, st') '(recv, st'')+ -> bytes+ }++{- |+Incremental decoding function.+-}+newtype Decode role' ps failure bytes = Decode+ { decode :: DecodeStep bytes failure (AnyMsg role' ps)+ }++{- |+Generic incremental decoder constructor, you need to convert specific incremental decoders to it.+-}+data DecodeStep bytes failure a+ = DecodePartial (Maybe bytes -> (DecodeStep bytes failure a))+ | DecodeDone a (Maybe bytes)+ | DecodeFail failure++data CodecFailure+ = CodecFailureOutOfInput+ | CodecFailure String+ deriving (Eq, Show)++instance Exception CodecFailure++{- |+Bottom functions for sending and receiving bytes.+-}+data Channel m bytes = Channel+ { send :: bytes -> m ()+ , recv :: m (Maybe bytes)+ }++{- |+Generic incremental decoding function.+-}+runDecoderWithChannel+ :: (Monad m)+ => Channel m bytes+ -> Maybe bytes+ -> DecodeStep bytes failure a+ -> m (Either failure (a, Maybe bytes))+runDecoderWithChannel Channel{recv} = go+ where+ go _ (DecodeDone x trailing) = return (Right (x, trailing))+ go _ (DecodeFail failure) = return (Left failure)+ go Nothing (DecodePartial k) = recv >>= pure . k >>= go Nothing+ go (Just trailing) (DecodePartial k) = (pure . k) (Just trailing) >>= go Nothing
+ src/TypedSession/Core.hs view
@@ -0,0 +1,254 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}++module TypedSession.Core where++import Data.IFunctor+import Data.IntMap (IntMap)+import Data.Kind++{- |++typed-session is a communication framework.+The messages received from the outside will be placed in MsgCache.+When interpreting the Peer, (Sing (r :: s)) will be generated according to the Peer's status.+SingToInt can convert Sing (r :: s) to Int.+Depending on this Int value, the required Msg is finally found from MsgCache.++In the process of multi-role communication, a message cache structure like MsgCache is needed.++Consider the following scenario++@+s1 s1 s2 Initial state+------------+a -> b a sends message MsgA to b+------------+s3 s2 s2 State after sending+------------+ b <- c c sends message MsgC to b+------------+s3 s4 s5 State after sending+@++For b, due to the influence of network transmission, it cannot guarantee that it can receive MsgA first and then MsgC.+If it receives MsgC first, it will directly put it in MsgCache and continue to wait until+MsgA arrives, then it will start to process MsgA first and then MsgC.++In general, dataToTag# is used directly here.++Example:++@+instance SingToInt Role where+ singToInt x = I# (dataToTag# x)++instance SingToInt PingPong where+ singToInt x = I# (dataToTag# x)+@+-}+class SingToInt s where+ singToInt :: Sing (r :: s) -> Int++{- |++Describes the type class of Msg. The core of typed-session.++@+type Done (sr :: role') :: ps+@++Describe the state of each role when it terminates.++@+data Msg role' ps (from :: ps) (sendAndSt :: (role', ps)) (recvAndSt :: (role', ps))+@+* role': the type of the role.+* ps: the type of the state machine.+* from: when sending a message, the sender is in this state,+where the receiver may be in this state, or a more generalized state related to this state.+For example, the sender is in state (S1 [True]), and the receiver is in state (S1 s).+* sendAndSt: the role that sends the message and the state of the role after sending the message.+* recvAndSt: the role that receives the message and the state of the role after receiving the message.++There are two principles to follow when designing the state of Msg:++1. When sending a message, the sender and receiver must be in the same state. Here the receiver may be in a more generalized state related to the state.+For example, the sender is in state (S1 [True]), and the receiver is in state (S1 s).++2. The same state can only be used for the same pair of receiver and sender.++For example, in the following example, state s1 is used for both (a -> b) and (b -> c), which is wrong.++@+s1 s1 s1+a -> b+s2 s1 s1+ b -> c+s2 s4 s5+@+-}+class (SingToInt role', SingToInt ps) => Protocol role' ps where+ type Done (sr :: role') :: ps+ data Msg role' ps (from :: ps) (sendAndSt :: (role', ps)) (recvAndSt :: (role', ps))++{- |+Package Msg and extract the required type.++@+Msg role' ps from '(send, sps) '(recv, rps)+Recv role' ps recv from rps+@+-}+data Recv role' ps recv from to where+ Recv+ :: Msg role' ps from '(send, sps) '(recv, rps)+ -> Recv role' ps recv from rps++{- |+Messages received from the outside are placed in MsgCache. When interpreting+Peer will use the Msg in MsgCache.+-}+type MsgCache role' ps = IntMap (AnyMsg role' ps)++{- |+Packaging of Msg, shielding part of the type information, mainly used for serialization.+-}+data AnyMsg role' ps where+ AnyMsg+ :: ( SingI recv+ , SingI st+ , SingToInt role'+ , SingToInt ps+ )+ => Msg role' ps st '(send, st') '(recv, st'')+ -> AnyMsg role' ps++msgFromStSing+ :: forall role' ps st send recv st' st''+ . (SingI recv, SingI st)+ => Msg role' ps st '(send, st') '(recv, st'')+ -> Sing st+msgFromStSing _ = sing @st++{- |+Core Ast, all we do is build this Ast and then interpret it.++@+IReturn :: ia st -> Peer role' ps r m ia st+@+IReturn indicates the termination of the continuation.++@+LiftM :: m (Peer role' ps r m ia st') -> Peer role' ps r m ia st+@++Liftm can transform state st to any state st'.+It looks a bit strange, as if it is a constructor that is not constrained by the Msg type.+Be careful when using it, it is a type breakpoint.+But some state transition functions need it, which can make the code more flexible.+Be very careful when using it!++@+Yield+ :: ( SingI recv+ , SingI from+ , SingToInt ps+ )+ => Msg role' ps from '(send, sps) '(recv, rps)+ -> Peer role' ps send m ia sps+ -> Peer role' ps send m ia from+@+Yield represents sending a message. Note that the Peer status changes from `from` to `sps`.++@+Await+ :: ( SingI recv+ , SingI from+ , SingToInt ps+ )+ => (Recv role' ps recv from ~> Peer role' ps recv m ia)+ -> Peer role' ps recv m ia from+@++Await represents receiving messages.+Different messages will lead to different states.+The state is passed to the next behavior through (~>).+-}+data Peer role' ps (r :: role') (m :: Type -> Type) (ia :: ps -> Type) (st :: ps) where+ IReturn :: ia st -> Peer role' ps r m ia st+ LiftM :: m (Peer role' ps r m ia st') -> Peer role' ps r m ia st+ Yield+ :: ( SingI recv+ , SingI from+ , SingToInt ps+ )+ => Msg role' ps from '(send, sps) '(recv, rps)+ -> Peer role' ps send m ia sps+ -> Peer role' ps send m ia from+ Await+ :: ( SingI recv+ , SingI from+ , SingToInt ps+ )+ => (Recv role' ps recv from ~> Peer role' ps recv m ia)+ -> Peer role' ps recv m ia from++instance (Functor m) => IMonadFail (Peer role' ps r m) where+ fail = error++instance (Functor m) => IFunctor (Peer role' ps r m) where+ imap f = \case+ IReturn ia -> IReturn (f ia)+ LiftM f' -> LiftM (fmap (imap f) f')+ Yield ms cont -> Yield ms (imap f cont)+ Await cont -> Await (imap f . cont)++instance (Functor m) => IMonad (Peer role' ps r m) where+ ireturn = IReturn+ ibind f = \case+ IReturn ia -> (f ia)+ LiftM f' -> LiftM (fmap (ibind f) f')+ Yield ms cont -> Yield ms (ibind f cont)+ Await cont -> Await (ibind f . cont)++{- |+Send a message, the Peer status changes from `from` to `sps`.+-}+yield+ :: ( Functor m+ , SingI recv+ , SingI from+ , SingToInt ps+ )+ => Msg role' ps from '(send, sps) '(recv, rps)+ -> Peer role' ps send m (At () sps) from+yield msg = Yield msg (returnAt ())++{- |+Receiving Messages.+-}+await+ :: ( Functor m+ , SingI recv+ , SingI from+ , SingToInt ps+ )+ => Peer role' ps recv m (Recv role' ps recv from) from+await = Await ireturn++{- |+Lift any m to Peer role' ps r m, which is an application of LiftM. +Note that the state of `ts` has not changed.+-}+liftm :: (Functor m) => m a -> Peer role' ps r m (At a ts) ts+liftm m = LiftM (returnAt <$> m)
+ src/TypedSession/Driver.hs view
@@ -0,0 +1,217 @@+-- This part of the code comes from typed-protocols, I modified a few things.+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE UndecidableInstances #-}+{-# OPTIONS_GHC -Wno-unused-do-bind #-}++{- |+Schematic diagram of the communication structure of three roles through typed-session:++<<data/fm.png>>++Some explanations for this diagram:++1. Roles are connected through channels, and there are many types of channels, such as channels established through TCP or channels established through TMVar.++2. Each role has a Peer thread, in which the Peer runs.++3. Each role has one or more decode threads, and the decoded Msgs are placed in the MsgCache.++4. SendMap aggregates the send functions of multiple Channels together.+When sending a message, the send function of the receiver is searched from SendMap.+-}+module TypedSession.Driver where++import Control.Concurrent.Class.MonadSTM+import Control.Monad.Class.MonadThrow (MonadThrow, throwIO)+import Data.IFunctor (At (..), Sing, SingI (sing))+import qualified Data.IFunctor as I+import Data.IntMap (IntMap)+import qualified Data.IntMap as IntMap+import GHC.Exception (Exception)+import TypedSession.Codec+import TypedSession.Core+import Unsafe.Coerce (unsafeCoerce)++{- |+Contains two functions sendMsg, recvMsg.+runPeerWithDriver uses them to send and receive Msg.+-}+data Driver role' ps m+ = Driver+ { sendMsg+ :: forall (send :: role') (recv :: role') (st :: ps) (st' :: ps) (st'' :: ps)+ . ( SingI recv+ , SingI st+ , SingToInt ps+ , SingToInt role'+ )+ => Sing recv+ -> Msg role' ps st '(send, st') '(recv, st'')+ -> m ()+ , recvMsg+ :: forall (st' :: ps)+ . (SingToInt ps)+ => Sing st'+ -> m (AnyMsg role' ps)+ }++{- |+Interpret Peer.+-}+runPeerWithDriver+ :: forall role' ps (r :: role') (st :: ps) m a+ . ( Monad m+ , (SingToInt role')+ )+ => Driver role' ps m+ -> Peer role' ps r m (At a (Done r)) st+ -> m a+runPeerWithDriver Driver{sendMsg, recvMsg} =+ go+ where+ go+ :: forall st'+ . Peer role' ps r m (At a (Done r)) st'+ -> m a+ go (IReturn (At a)) = pure a+ go (LiftM k) = k >>= go+ go (Yield (msg :: Msg role' ps (st' :: ps) '(r, sps) '(recv :: role', rps)) k) = do+ sendMsg (sing @recv) msg+ go k+ go (Await (k :: (Recv role' ps r st' I.~> Peer role' ps r m ia))) = do+ AnyMsg msg <- recvMsg (sing @st')+ go (k $ unsafeCoerce (Recv msg))++{- |+A wrapper around AnyMsg that represents sending and receiving Msg.+-}+data TraceSendRecv role' ps where+ TraceSendMsg :: AnyMsg role' ps -> TraceSendRecv role' ps+ TraceRecvMsg :: AnyMsg role' ps -> TraceSendRecv role' ps++instance (Show (AnyMsg role' ps)) => Show (TraceSendRecv role' ps) where+ show (TraceSendMsg msg) = "Send " ++ show msg+ show (TraceRecvMsg msg) = "Recv " ++ show msg++{- |+Similar to the log function, used to print received or sent messages.+-}+type Tracer role' ps m = TraceSendRecv role' ps -> m ()++{- |+The default trace function. It simply ignores everything.+-}+nullTracer :: (Monad m) => a -> m ()+nullTracer _ = pure ()++{- |+SendMap aggregates the send functions of multiple Channels together.+When sending a message, the send function of the receiver is found from SendMap.+-}+type SendMap role' m bytes = IntMap (bytes -> m ())++{- |++Build Driver through SendMap and MsgCache.+Here we need some help from other functions:++1. `Tracer role' ps n` is similar to the log function, used to print received or sent messages.+2. `Encode role' ps` bytes encoding function, converts Msg into bytes.+3. `forall a. n a -> m a` This is a bit complicated, I will explain it in detail below.++I see Peer as three layers:++1. `Peer` upper layer, meets the requirements of McBride Indexed Monad, uses do syntax construction, has semantic checks, and is interpreted to the second layer m through runPeerWithDriver.+2. `m` middle layer, describes the business requirements in this layer, and converts the received Msg into specific business actions.+3. `n` bottom layer, responsible for receiving and sending bytes. It can have multiple options such as IO or IOSim. Using IOSim can easily test the code.+-}+driverSimple+ :: forall role' ps bytes m n+ . ( Monad m+ , Monad n+ , MonadSTM n+ )+ => Tracer role' ps n+ -> Encode role' ps bytes+ -> SendMap role' n bytes+ -> TVar n (MsgCache role' ps)+ -> (forall a. n a -> m a)+ -> Driver role' ps m+driverSimple tracer Encode{encode} sendMap tvar liftFun =+ Driver{sendMsg, recvMsg}+ where+ sendMsg+ :: forall (send :: role') (recv :: role') (from :: ps) (st :: ps) (st1 :: ps)+ . ( SingI recv+ , SingI from+ , SingToInt ps+ , SingToInt role'+ )+ => Sing recv+ -> Msg role' ps from '(send, st) '(recv, st1)+ -> m ()+ sendMsg role msg = liftFun $ do+ case IntMap.lookup (singToInt role) sendMap of+ Nothing -> error "np"+ Just sendFun -> sendFun (encode msg)+ tracer (TraceSendMsg (AnyMsg msg))++ recvMsg+ :: forall (st' :: ps)+ . (SingToInt ps)+ => Sing st'+ -> m (AnyMsg role' ps)+ recvMsg sst' = do+ let singInt = singToInt sst'+ liftFun $ do+ anyMsg <- atomically $ do+ agencyMsg <- readTVar tvar+ case IntMap.lookup singInt agencyMsg of+ Nothing -> retry+ Just v -> do+ writeTVar tvar (IntMap.delete singInt agencyMsg)+ pure v+ tracer (TraceRecvMsg (anyMsg))+ pure anyMsg++{- |+decode loop, usually in a separate thread.++The decoded Msg is placed in MsgCache.++@+data Msg role' ps (from :: ps) (sendAndSt :: (role', ps)) (recvAndSt :: (role', ps))+@+Note that when placing a new Msg in MsgCache, if a Msg with the same `from` already exists in MsgCache, the decoding process will be blocked,+until that Msg is consumed before placing the new Msg in MsgCache.++This usually happens when the efficiency of Msg generation is greater than the efficiency of consumption.+-}+decodeLoop+ :: (Exception failure, MonadSTM n, MonadThrow n)+ => Tracer role' ps n+ -> Maybe bytes+ -> Decode role' ps failure bytes+ -> Channel n bytes+ -> TVar n (MsgCache role' ps)+ -> n ()+decodeLoop tracer mbt d@Decode{decode} channel tvar = do+ result <- runDecoderWithChannel channel mbt decode+ case result of+ Right (AnyMsg msg, mbt') -> do+ let agencyInt = singToInt $ msgFromStSing msg+ atomically $ do+ agencyMsg <- readTVar tvar+ case IntMap.lookup agencyInt agencyMsg of+ Nothing -> writeTVar tvar (IntMap.insert agencyInt (AnyMsg msg) agencyMsg)+ Just _v -> retry+ decodeLoop tracer mbt' d channel tvar+ Left failure -> throwIO failure
+ src/TypedSession/TH.hs view
@@ -0,0 +1,293 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TypeApplications #-}+{-# OPTIONS_GHC -Wno-unused-imports #-}+{-# OPTIONS_GHC -split-sections #-}++module TypedSession.TH where++import Control.Monad (forM)+import Data.Either (fromRight)+import Data.Kind+import qualified Data.Set as Set+import Language.Haskell.TH hiding (Type)+import qualified Language.Haskell.TH as TH+import Language.Haskell.TH.Quote+import TypedSession.State.GenDoc+import TypedSession.State.Parser (runProtocolParser)+import TypedSession.State.Piple (PipleResult (..), piple)+import TypedSession.State.Render+import TypedSession.State.Type (BranchSt (..), Creat, MsgOrLabel (..), MsgT1, Protocol (..), ProtocolError, T (..))++roleDecs :: Name -> Q [Dec]+roleDecs name = do+ res <- reify name+ case res of+ TyConI (DataD [] dName [] Nothing cons _) -> do+ a <- newName "a"+ x <- newName "x"+ pure $+ [ DataD+ []+ (addS dName)+ [KindedTV a BndrReq (ConT dName)]+ Nothing+ [GadtC [addS n] [] (AppT (ConT (addS dName)) (PromotedT n)) | NormalC n [] <- cons]+ []+ , TySynInstD (TySynEqn Nothing (ConT (mkName "Data.IFunctor.Sing")) (ConT (addS dName)))+ ]+ ++ [ InstanceD+ Nothing+ []+ (AppT (ConT $ mkName "Data.IFunctor.SingI") (PromotedT n))+ [FunD (mkName "sing") [Clause [] (NormalB (ConE (addS n))) []]]+ | NormalC n [] <- cons+ ]+ ++ [ InstanceD+ Nothing+ []+ (AppT (ConT (mkName "TypedSession.Core.SingToInt")) (ConT name))+ [ FunD+ (mkName "singToInt")+ [Clause [VarP x] (NormalB (AppE (ConE (mkName "I#")) (AppE (VarE $ mkName "dataToTag#") (VarE x)))) []]+ ]+ ]+ _ -> error "np"++addS :: Name -> Name+addS name =+ let n = (nameBase name)+ in mkName ("S" <> n)++protDecsAndMsgDecs :: forall r bst. (Show r, Show bst) => String -> Name -> Name -> PipleResult r bst -> Q [Dec]+protDecsAndMsgDecs protN roleName bstName PipleResult{msgT1, dnySet, stBound = (fromVal, toVal)} = do+ sVar <- newName "s"+ let protName = mkName protN+ protSName = mkName ("S" <> protN)+ genConstr i =+ if i == -1+ then NormalC (mkName "End") []+ else+ if i `Set.member` dnySet+ then+ NormalC+ (mkName $ "S" <> show i)+ [(Bang NoSourceUnpackedness NoSourceStrictness, ConT bstName)]+ else NormalC (mkName $ "S" <> show i) []+ genSConstr i =+ if i == -1+ then+ GadtC [mkName "SEnd"] [] (AppT (ConT protSName) (PromotedT (mkName "End")))+ else+ if i `Set.member` dnySet+ then+ ForallC+ [KindedTV sVar SpecifiedSpec (ConT bstName)]+ []+ ( GadtC+ [mkName $ "SS" <> show i]+ []+ (AppT (ConT protSName) (AppT (PromotedT $ mkName $ "S" <> show i) (VarT sVar)))+ )+ else+ GadtC [mkName $ "SS" <> show i] [] (AppT (ConT protSName) (PromotedT $ mkName $ "S" <> show i))++ isTAny :: T bst -> Bool+ isTAny = \case+ TAny _ -> True+ _ -> False++ mkInstanceMsg :: Name -> Protocol (MsgT1 r bst) r bst -> Q [Con]+ mkInstanceMsg s = \case+ Msg ((a, b, c), (from, to), _) constr args _ _ :> prots -> do+ let tAnyToType :: T bst -> TH.Type+ tAnyToType = \case+ TNum i -> PromotedT (mkName $ "S" <> show i)+ BstList i bst -> AppT (PromotedT (mkName $ "S" <> show i)) (PromotedT (mkName (show bst)))+ TAny i -> AppT (PromotedT (mkName $ "S" <> show i)) (VarT s)+ TEnd -> PromotedT $ mkName "End"++ let mkTName =+ ( AppT+ ( AppT+ ( AppT+ ( AppT+ ( AppT+ (ConT $ mkName "Msg")+ (ConT roleName)+ )+ (ConT protName)+ )+ (tAnyToType a)+ )+ (AppT (AppT (PromotedTupleT 2) (PromotedT (mkName (show from)))) (tAnyToType b))+ )+ (AppT (AppT (PromotedTupleT 2) (PromotedT (mkName (show to)))) (tAnyToType c))+ )+ let val =+ let gadtc =+ GadtC+ [mkName constr]+ [ ( Bang NoSourceUnpackedness NoSourceStrictness+ , case words ag of+ [] -> error "np"+ (x : xs) -> foldl' AppT (ConT (mkName x)) (map (ConT . mkName) xs)+ )+ | ag <- args+ ]+ mkTName+ in if any isTAny [a, b, c]+ then+ ForallC+ [KindedTV s SpecifiedSpec (ConT bstName)]+ []+ gadtc+ else gadtc+ res <- mkInstanceMsg s prots+ pure (val : res)+ Label _ _ :> prots -> mkInstanceMsg s prots+ Branch _ _ ls -> do+ ls' <- forM ls $ \(BranchSt _ _ prot) -> mkInstanceMsg s prot+ pure $ concat ls'+ _ -> pure []++ a <- newName "a"+ s1 <- newName "s1"+ x <- newName "x"++ -- make instance Done+ res <- reify roleName+ instDoneDesc <- case res of+ TyConI (DataD [] _ [] Nothing cons _) -> do+ pure+ [ TySynInstD+ ( TySynEqn+ Nothing+ (AppT (ConT (mkName "Done")) (PromotedT n))+ (PromotedT (mkName "End"))+ )+ | NormalC n [] <- cons+ ]+ _ -> error "np"++ -- make instance msg+ ss <- newName "s"+ instMsgDesc <- mkInstanceMsg ss (msgT1)++ fromVar <- newName "from"+ sendVar <- newName "send"+ recvVar <- newName "recv"+ pure $+ [ DataD [] protName [] Nothing [genConstr i | i <- [fromVal .. toVal]] []+ ]+ ++ [DataD [] protSName [KindedTV a BndrReq (ConT protName)] Nothing [genSConstr i | i <- [fromVal .. toVal]] []]+ ++ [TySynInstD (TySynEqn Nothing (ConT (mkName "Data.IFunctor.Sing")) (ConT protSName))]+ ++ [ InstanceD+ Nothing+ []+ ( AppT+ (ConT $ mkName "Data.IFunctor.SingI")+ ( if i == -1+ then PromotedT (mkName "End")+ else+ if i `Set.member` dnySet+ then SigT (AppT (PromotedT (mkName ("S" <> show i))) (VarT s1)) (ConT protName)+ else PromotedT (mkName ("S" <> show i))+ )+ )+ [ FunD+ (mkName "sing")+ [Clause [] (NormalB (ConE (mkName $ "S" <> (if i == -1 then "End" else ("S" <> show i))))) []]+ ]+ | i <- [fromVal .. toVal]+ ]+ ++ [ InstanceD+ Nothing+ []+ (AppT (ConT (mkName "TypedSession.Core.SingToInt")) (ConT protName))+ [ FunD+ (mkName "singToInt")+ [Clause [VarP x] (NormalB (AppE (ConE (mkName "I#")) (AppE (VarE $ mkName "dataToTag#") (VarE x)))) []]+ ]+ ]+ ++ [ InstanceD+ Nothing+ []+ ( AppT+ (AppT (ConT (mkName "TypedSession.Core.Protocol")) (ConT roleName))+ (ConT protName)+ )+ ( instDoneDesc+ ++ let ct1 = (AppT (AppT (TupleT 2) (ConT roleName)) (ConT protName))+ in [ DataInstD+ []+ ( Just+ [ KindedTV fromVar () (ConT protName)+ , KindedTV sendVar () ct1+ , KindedTV recvVar () ct1+ ]+ )+ ( AppT+ ( AppT+ ( AppT+ ( AppT+ ( AppT+ (ConT $ mkName "Msg")+ (ConT roleName)+ )+ (ConT protName)+ )+ (SigT (VarT fromVar) (ConT protName))+ )+ (SigT (VarT sendVar) ct1)+ )+ (SigT (VarT recvVar) ct1)+ )+ Nothing+ instMsgDesc+ []+ ]+ )+ ]++protocol+ :: forall r bst+ . ( Enum r+ , Bounded r+ , Show r+ , Enum bst+ , Bounded bst+ , Show bst+ , Ord r+ )+ => String -> Name -> Name -> QuasiQuoter+protocol protN roleName bstName =+ QuasiQuoter+ { quoteExp = const $ fail "No protocol parse for exp"+ , quotePat = const $ fail "No protocol parse for pat"+ , quoteType = const $ fail "No protocol parser for type"+ , quoteDec = parseOrThrow+ }+ where+ parseOrThrow :: String -> Q [Dec]+ parseOrThrow st = case runProtocolParser @r @bst st of+ Left e -> fail (show e)+ Right protCreat -> case piple protCreat of+ Left e -> fail (show e)+ Right pipResult -> do+ let graphStr = genGraph @r @bst defaultStrFilEnv pipResult+ runIO $ do+ writeFile (protN <> ".prot") graphStr+ putStrLn graphStr+ d1 <- roleDecs roleName+ d2 <- protDecsAndMsgDecs protN roleName bstName pipResult+ pure (d1 ++ d2)
test/Book.hs view
@@ -28,9 +28,9 @@ import Data.Kind import GHC.Exts (dataToTag#) import GHC.Int (Int (I#))-import TypedProtocol.Codec-import TypedProtocol.Core-import TypedProtocol.Driver+import TypedSession.Codec+import TypedSession.Core+import TypedSession.Driver {-
test/Book1.hs view
@@ -28,9 +28,9 @@ import Data.Kind import GHC.Exts (dataToTag#) import GHC.Int (Int (I#))-import TypedProtocol.Codec-import TypedProtocol.Core-import TypedProtocol.Driver+import TypedSession.Codec+import TypedSession.Core+import TypedSession.Driver {-
test/Book2.hs view
@@ -28,9 +28,9 @@ import Data.Kind import GHC.Exts (dataToTag#) import GHC.Int (Int (I#))-import TypedProtocol.Codec-import TypedProtocol.Core-import TypedProtocol.Driver+import TypedSession.Codec+import TypedSession.Core+import TypedSession.Driver {-
test/Book3/Main.hs view
@@ -16,6 +16,7 @@ module Book3.Main where import Book3.Peer+import Book3.Protocol import Book3.Type import Control.Carrier.Random.Gen (runRandom) import Control.Concurrent.Class.MonadSTM@@ -24,12 +25,12 @@ import Control.Monad.Class.MonadFork (MonadFork, forkIO) import Control.Monad.Class.MonadSay import Control.Monad.Class.MonadThrow (MonadThrow)+import Control.Monad.IOSim import qualified Data.IntMap as IntMap import System.Random (StdGen, split)-import TypedProtocol.Codec-import TypedProtocol.Core-import TypedProtocol.Driver-import Control.Monad.IOSim+import TypedSession.Codec+import TypedSession.Core+import TypedSession.Driver mvarsAsChannel :: (MonadSTM m)@@ -42,7 +43,7 @@ send x = atomically (putTMVar bufferWrite x) recv = atomically (Just <$> takeTMVar bufferRead) -myTracer :: (MonadSay m) => String -> Tracer Role BookSt m+myTracer :: (MonadSay m) => String -> Tracer BookRole Book m myTracer st v = say (st <> show v) runAll@@ -56,9 +57,9 @@ => StdGen -> n () runAll g = do- buyerTMVar <- newEmptyTMVarIO @n @(AnyMsg Role BookSt)- buyer2TMVar <- newEmptyTMVarIO @n @(AnyMsg Role BookSt)- sellerTMVar <- newEmptyTMVarIO @n @(AnyMsg Role BookSt)+ buyerTMVar <- newEmptyTMVarIO @n @(AnyMsg BookRole Book)+ buyer2TMVar <- newEmptyTMVarIO @n @(AnyMsg BookRole Book)+ sellerTMVar <- newEmptyTMVarIO @n @(AnyMsg BookRole Book) let buyerSellerChannel = mvarsAsChannel @n buyerTMVar sellerTMVar buyerBuyer2Channel = mvarsAsChannel @n buyerTMVar buyer2TMVar
test/Book3/Peer.hs view
@@ -15,40 +15,43 @@ module Book3.Peer where +import Book3.Protocol import Book3.Type import Control.Algebra (Has) import Control.Effect.Random (Random, uniform) import Data.IFunctor (At (..), ireturn, returnAt) import qualified Data.IFunctor as I import Data.Kind-import TypedProtocol.Core+import TypedSession.Core budget :: Int budget = 16 -data CheckPriceResult :: BookSt -> Type where- Yes :: CheckPriceResult (S3 [Enough, Support, Two, Found])- No :: CheckPriceResult (S3 [NotEnough, Support, Two, Found])+type Date = Int +data CheckPriceResult :: Book -> Type where+ Yes :: CheckPriceResult (S5 Enough)+ No :: CheckPriceResult (S5 NotEnough)+ checkPrice :: (Has Random sig m) => Int -> Int- -> Peer Role BookSt Buyer m CheckPriceResult (S3 s)+ -> Peer BookRole Book Buyer m CheckPriceResult S8 checkPrice _i _h = I.do At b <- liftm $ uniform @Bool if b then LiftM $ pure (ireturn Yes) else LiftM $ pure (ireturn No) -data OT :: BookSt -> Type where- OTOne :: OT (S1 [One, Found])- OTTwo :: OT (S1 [Two, Found])+data OT :: Book -> Type where+ OTOne :: OT (S5 One)+ OTTwo :: OT (S1 Two) choiceOT :: (Has Random sig m) => Int- -> Peer Role BookSt Buyer m OT (S1 s)+ -> Peer BookRole Book Buyer m OT S4 choiceOT _i = I.do At b <- liftm $ uniform @Bool if b@@ -57,7 +60,7 @@ buyerPeer :: (Has Random sig m)- => Peer Role BookSt Buyer m (At (Maybe Date) (Done Buyer)) S0+ => Peer BookRole Book Buyer m (At (Maybe Date) (Done Buyer)) S0 buyerPeer = I.do yield (Title "haskell book") await I.>>= \case@@ -67,7 +70,6 @@ Recv (Price i) -> I.do choiceOT i I.>>= \case OTOne -> I.do- yield OneAfford yield OneAccept Recv (OneDate d) <- await yield (OneSuccess d)@@ -76,7 +78,7 @@ where f1 :: (Has Random sig m)- => Peer Role BookSt 'Buyer m (At (Maybe Date) (Done Buyer)) ('S1 '[ 'Two, 'Found])+ => Peer BookRole Book 'Buyer m (At (Maybe Date) (Done Buyer)) ('S1 'Two) f1 = I.do yield (PriceToBuyer2 300) await I.>>= \case@@ -95,14 +97,14 @@ yield TwoFailed returnAt Nothing -data BuySupp :: BookSt -> Type where- BNS :: BuySupp (S6 '[NotSupport, Two, Found])- BS :: BuySupp (S6 '[Support, Two, Found])+data BuySupp :: Book -> Type where+ BNS :: BuySupp (S6 NotSupport)+ BS :: BuySupp (S6 Support) choiceB :: (Has Random sig m) => Int- -> Peer Role BookSt Buyer2 m BuySupp (S6 s)+ -> Peer BookRole Book Buyer2 m BuySupp S7 choiceB _i = I.do At b <- liftm $ uniform @Bool if b@@ -111,13 +113,11 @@ buyer2Peer :: (Has Random sig m)- => Peer Role BookSt Buyer2 m (At (Maybe Date) (Done Buyer2)) (S1 s)+ => Peer BookRole Book Buyer2 m (At (Maybe Date) (Done Buyer2)) (S1 s) buyer2Peer = I.do await I.>>= \case Recv SellerNoBook -> returnAt Nothing- Recv OneAfford -> I.do- Recv (OneSuccess d) <- await- returnAt (Just d)+ Recv (OneSuccess d) -> returnAt (Just d) Recv (PriceToBuyer2 i) -> I.do choiceB i I.>>= \case BNS -> I.do@@ -129,14 +129,14 @@ Recv (TwoSuccess d) -> returnAt $ Just d Recv TwoFailed -> returnAt Nothing -data FindBookResult :: BookSt -> Type where- NotFound' :: FindBookResult (S2 '[NotFound])- Found' :: FindBookResult (S2 '[Found])+data FindBookResult :: Book -> Type where+ NotFound' :: FindBookResult (S2 NotFound)+ Found' :: FindBookResult (S2 Found) findBook :: (Has Random sig m) => String- -> Peer Role BookSt Seller m FindBookResult (S2 s)+ -> Peer BookRole Book Seller m FindBookResult S3 findBook _st = I.do At b <- liftm $ uniform @Bool if b@@ -145,7 +145,7 @@ sellerPeer :: (Has Random sig m)- => Peer Role BookSt Seller m (At () (Done Seller)) S0+ => Peer BookRole Book Seller m (At () (Done Seller)) S0 sellerPeer = I.do Recv (Title st) <- await findBook st I.>>= \case
+ test/Book3/Protocol.hs view
@@ -0,0 +1,143 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE MagicHash #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeFamilies #-}+{-# OPTIONS_GHC -Wno-orphans #-}++module Book3.Protocol where++import Book3.Type++import Data.IFunctor (Sing, SingI (sing))+import GHC.Exts (Int (..), dataToTag#)+import TypedSession.Codec+import TypedSession.Core++[bookProtocol|+ Label 0+ Msg "Title" ["String"] Buyer Seller+ Branch Seller {+ BranchSt NotFound+ Msg "NoBook" [] Seller Buyer+ Msg "SellerNoBook" [] Buyer Buyer2+ Terminal+ BranchSt Found + Msg "Price" ["Int"] Seller Buyer+ Branch Buyer {+ BranchSt One + Msg "OneAccept" [] Buyer Seller+ Msg "OneDate" ["Int"] Seller Buyer+ Msg "OneSuccess" ["Int"] Buyer Buyer2+ Terminal+ BranchSt Two + Msg "PriceToBuyer2" ["Int"] Buyer Buyer2+ Branch Buyer2 {+ BranchSt NotSupport + Msg "NotSupport1" [] Buyer2 Buyer+ Msg "TwoNotBuy" [] Buyer Seller+ Terminal+ BranchSt Support + Msg "SupportVal" ["Int"] Buyer2 Buyer+ Branch Buyer {+ BranchSt Enough + Msg "TwoAccept" [] Buyer Seller+ Msg "TwoDate" ["Int"] Seller Buyer+ Msg "TwoSuccess" ["Int"] Buyer Buyer2+ Terminal+ BranchSt NotEnough + Msg "TwoNotBuy1" [] Buyer Seller+ Msg "TwoFailed" [] Buyer Buyer2+ Terminal + }+ }+ }+ }++|]++{-+-----------------------------------------------Buyer-------------------------Seller------------------------Buyer2+LABEL 0 S0 S0 S1 s+ Title S0-> ->S0 S1 s+ [Branch Seller] S2 s S3 S1 s+ NoBook S2 s<- <-{S2 NotFound} S1 s+ SellerNoBook S1 NotFound-> End ->S1 s+ Terminal End End End+ Price S2 s<- <-{S2 Found} S1 s+ [Branch Buyer] S4 S5 s S1 s+ OneAccept {S5 One}-> ->S5 s S1 s+ OneDate S10<- <-S10 S1 s+ OneSuccess S1 One-> End ->S1 s+ Terminal End End End+ PriceToBuyer2 {S1 Two}-> S5 s ->S1 s+ [Branch Buyer2] S6 s S5 s S7+ NotSupport1 S6 s<- S5 s <-{S6 NotSupport}+ TwoNotBuy S5 NotSupport-> ->S5 s End+ Terminal End End End+ SupportVal S6 s<- S5 s <-{S6 Support}+ [Branch Buyer] S8 S5 s S9 s+ TwoAccept {S5 Enough}-> ->S5 s S9 s+ TwoDate S11<- <-S11 S9 s+ TwoSuccess S9 Enough-> End ->S9 s+ Terminal End End End+ TwoNotBuy1 {S5 NotEnough}-> ->S5 s S9 s+ TwoFailed S9 NotEnough-> End ->S9 s+ Terminal End End End++-}++encodeMsg :: Encode BookRole Book (AnyMsg BookRole Book)+encodeMsg = Encode $ \x -> case x of+ Title{} -> AnyMsg x+ NoBook{} -> AnyMsg x+ SellerNoBook{} -> AnyMsg x+ Price{} -> AnyMsg x+ OneAccept{} -> AnyMsg x+ OneDate{} -> AnyMsg x+ OneSuccess{} -> AnyMsg x+ PriceToBuyer2{} -> AnyMsg x+ NotSupport1{} -> AnyMsg x+ TwoNotBuy{} -> AnyMsg x+ SupportVal{} -> AnyMsg x+ TwoAccept{} -> AnyMsg x+ TwoDate{} -> AnyMsg x+ TwoSuccess{} -> AnyMsg x+ TwoNotBuy1{} -> AnyMsg x+ TwoFailed{} -> AnyMsg x++decodeMsg+ :: DecodeStep+ (AnyMsg BookRole Book)+ CodecFailure+ (AnyMsg BookRole Book)+decodeMsg =+ DecodePartial $ \case+ Nothing -> DecodeFail (CodecFailure "expected more data")+ Just anyMsg -> DecodeDone anyMsg Nothing++instance Show (AnyMsg BookRole Book) where+ show (AnyMsg msg) = case msg of+ Title st -> "Title " <> show st+ NoBook -> "NoBook"+ SellerNoBook -> "SellerNoBook"+ Price i -> "Price " <> show i+ OneAccept -> "OneAccept"+ OneDate d -> "OneDate " <> show d+ OneSuccess d -> "OneSuccess" <> show d+ PriceToBuyer2 i -> "PriceToBuyer2 " <> show i+ NotSupport1 -> "NotSupport1"+ TwoNotBuy -> "TwoNotBuy"+ SupportVal v -> "SupportVal " <> show v+ TwoAccept -> "TwoAccept"+ TwoDate d -> "TwoDate " <> show d+ TwoSuccess d -> "TwoSuccess " <> show d+ TwoNotBuy1 -> "TwoNotBuy1"+ TwoFailed -> "TwoFailed"
test/Book3/Type.hs view
@@ -10,101 +10,17 @@ {-# LANGUAGE QualifiedDo #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} module Book3.Type where -import Data.IFunctor (Sing, SingI)-import qualified Data.IFunctor as I-import Data.Kind-import GHC.Exts (dataToTag#)-import GHC.Int (Int (I#))-import TypedProtocol.Codec-import TypedProtocol.Core--{-----------------------------Buyer-------------------------Seller------------------------Buyer2---------------------------- S0 S0 (S1 s)- Title | -----> |- (S2 s) (S2 s) (S1 s)- ---------------------------------------------------[NotFound]---------------------------------------------------- (S2 s) (S2 [NotFound]) (S1 s)- NoBook | <----- |- (S1 [NotFound]) End (S1 s)- SellerNoBook | -----> |- End End End- Terminal-- ----------------------------------------------------[Found]------------------------------------------------------ (S2 s) (S2 [Found]) (S1 s)- Price | <----- |- (S1 s) (S3 s) (S1 s)- ----------------------------------------------[One,Found]------------------------------------------------ (S1 [One,Found]) (S3 s) (S1 s)- OneAfford | -----> |- (S3 [One,Found]) (S3 s) S4- OneAccept | -----> |- S5 S5 S4- OneDate | <----- |- S4 End S4- OneSuccess | -----> |- End End End- Terminal-- ----------------------------------------------[Two,Found]------------------------------------------------ (S1 [Two,Found]) (S3 s) (S1 s)- PriceToBuyer2 | -----> |- (S6 s) (S3 s) (S6 s)- -------------------------------------[NotSupport,Two,Found]-------------------------------------- (S6 s) (S3 s) (S6 [NotSupport,Two,Found])- NotSupport1 | <----- |- (S3 [NotSupport,Two,Found]) (S3 s) End- TwoNotBuy | -----> |- End End End- Terminal-- --------------------------------------[Support,Two,Found]---------------------------------------- (S6 s) (S3 s) (S6 [Support,Two,Found])- SupportVal | <----- |- (S3 s) (S3 s) (S7 s)- -------------------------------[Enough,Support,Two,Found]-------------------------------- (S3 [Enough,Support,Two,Found]) (S3 s) (S7 s)- TwoAccept | -----> |- S8 S8 (S7 s)- TwoDate | <----- |- (S7 [Enough,Support,Two,Found]) End (S7 s)- TwoSuccess | -----> |- End End End- Terminal-- -----------------------------[NotEnough,Support,Two,Found]------------------------------- (S3 [NotEnough,Support,Two,Found]) (S3 s) (S7 s)- TwoNotBuy1 | -----> |- (S7 [NotEnough,Support,Two,Found]) End (S7 s)- TwoFailed | -----> |- End End End- Terminal---}--data Role = Buyer | Seller | Buyer2- deriving (Show, Eq, Ord)--data SRole :: Role -> Type where- SBuyer :: SRole Buyer- SBuyer2 :: SRole Buyer2- SSeller :: SRole Seller--type instance Sing = SRole--instance SingI Buyer where- sing = SBuyer--instance SingI Buyer2 where- sing = SBuyer2+import Language.Haskell.TH.Quote (QuasiQuoter)+import TypedSession.TH (protocol) -instance SingI Seller where- sing = SSeller+data BookRole = Buyer | Seller | Buyer2+ deriving (Show, Eq, Ord, Enum, Bounded) data BookBranchSt = NotFound@@ -115,142 +31,13 @@ | NotSupport | Enough | NotEnough- deriving (Show)--data BookSt- = S0- | S1 [BookBranchSt]- | S2 [BookBranchSt]- | S3 [BookBranchSt]- | S4- | S5- | S6 [BookBranchSt]- | S7 [BookBranchSt]- | S8- | End--data SBookSt :: BookSt -> Type where- SS0 :: SBookSt S0- SS1 :: SBookSt (S1 s)- SS2 :: SBookSt (S2 s)- SS3 :: SBookSt (S3 s)- SS4 :: SBookSt S4- SS5 :: SBookSt S5- SS6 :: SBookSt (S6 s)- SS7 :: SBookSt (S7 s)- SS8 :: SBookSt S8- SEnd :: SBookSt End--type instance Sing = SBookSt--instance SingI S0 where- sing = SS0--instance SingI (S1 s) where- sing = SS1--instance SingI (S2 s) where- sing = SS2--instance SingI (S3 s) where- sing = SS3--instance SingI S4 where- sing = SS4--instance SingI S5 where- sing = SS5--instance SingI (S6 s) where- sing = SS6--instance SingI (S7 s) where- sing = SS7--instance SingI S8 where- sing = SS8--instance SingI End where- sing = SEnd--type Date = Int--instance SingToInt Role where- singToInt x = I# (dataToTag# x)--instance SingToInt BookSt where- singToInt x = I# (dataToTag# x)--instance Protocol Role BookSt where- type Done Buyer = End- type Done Seller = End- type Done Buyer2 = End-- data Msg Role BookSt from send recv where- Title :: String -> Msg Role BookSt S0 '(Buyer, S2 s) '(Seller, S2 s)- NoBook :: Msg Role BookSt (S2 '[NotFound]) '(Seller, End) '(Buyer, S1 '[NotFound])- SellerNoBook :: Msg Role BookSt (S1 '[NotFound]) '(Buyer, End) '(Buyer2, End)- Price :: Int -> Msg Role BookSt (S2 '[Found]) '(Seller, S3 s) '(Buyer, S1 s)- OneAfford :: Msg Role BookSt (S1 '[One, Found]) '(Buyer, S3 '[One, Found]) '(Buyer2, S4)- OneAccept :: Msg Role BookSt (S3 '[One, Found]) '(Buyer, S5) '(Seller, S5)- OneDate :: Date -> Msg Role BookSt S5 '(Seller, End) '(Buyer, S4)- OneSuccess :: Date -> Msg Role BookSt S4 '(Buyer, End) '(Buyer2, End)- PriceToBuyer2 :: Int -> Msg Role BookSt (S1 '[Two, Found]) '(Buyer, S6 s) '(Buyer2, S6 s)- NotSupport1 :: Msg Role BookSt (S6 '[NotSupport, Two, Found]) '(Buyer2, End) '(Buyer, S3 '[NotSupport, Two, Found])- TwoNotBuy :: Msg Role BookSt (S3 '[NotSupport, Two, Found]) '(Buyer, End) '(Seller, End)- SupportVal :: Int -> Msg Role BookSt (S6 '[Support, Two, Found]) '(Buyer2, S7 s) '(Buyer, S3 s)- TwoAccept :: Msg Role BookSt (S3 '[Enough, Support, Two, Found]) '(Buyer, S8) '(Seller, S8)- TwoDate :: Date -> Msg Role BookSt S8 '(Seller, End) '(Buyer, S7 '[Enough, Support, Two, Found])- TwoSuccess :: Date -> Msg Role BookSt (S7 '[Enough, Support, Two, Found]) '(Buyer, End) '(Buyer2, End)- TwoNotBuy1 :: Msg Role BookSt (S3 '[NotEnough, Support, Two, Found]) '(Buyer, S7 '[NotEnough, Support, Two, Found]) '(Seller, End)- TwoFailed :: Msg Role BookSt (S7 '[NotEnough, Support, Two, Found]) '(Buyer, End) '(Buyer2, End)--encodeMsg :: Encode Role BookSt (AnyMsg Role BookSt)-encodeMsg = Encode $ \x -> case x of- Title{} -> AnyMsg x- NoBook{} -> AnyMsg x- SellerNoBook{} -> AnyMsg x- Price{} -> AnyMsg x- OneAfford{} -> AnyMsg x- OneAccept{} -> AnyMsg x- OneDate{} -> AnyMsg x- OneSuccess{} -> AnyMsg x- PriceToBuyer2{} -> AnyMsg x- NotSupport1{} -> AnyMsg x- TwoNotBuy{} -> AnyMsg x- SupportVal{} -> AnyMsg x- TwoAccept{} -> AnyMsg x- TwoDate{} -> AnyMsg x- TwoSuccess{} -> AnyMsg x- TwoNotBuy1{} -> AnyMsg x- TwoFailed{} -> AnyMsg x--decodeMsg- :: DecodeStep- (AnyMsg Role BookSt)- CodecFailure- (AnyMsg Role BookSt)-decodeMsg =- DecodePartial $ \case- Nothing -> DecodeFail (CodecFailure "expected more data")- Just anyMsg -> DecodeDone anyMsg Nothing+ deriving (Show, Eq, Ord, Enum, Bounded) -instance Show (AnyMsg Role BookSt) where- show (AnyMsg msg) = case msg of- Title st -> "Title " <> show st- NoBook -> "NoBook"- SellerNoBook -> "SellerNoBook"- Price i -> "Price " <> show i- OneAfford -> "OneAfford"- OneAccept -> "OneAccept"- OneDate d -> "OneDate " <> show d- OneSuccess d -> "OneSuccess" <> show d- PriceToBuyer2 i -> "PriceToBuyer2 " <> show i- NotSupport1 -> "NotSupport1"- TwoNotBuy -> "TwoNotBuy"- SupportVal v -> "SupportVal " <> show v- TwoAccept -> "TwoAccept"- TwoDate d -> "TwoDate " <> show d- TwoSuccess d -> "TwoSuccess " <> show d- TwoNotBuy1 -> "TwoNotBuy1"- TwoFailed -> "TwoFailed"+bookProtocol :: QuasiQuoter+bookProtocol =+ protocol+ @BookRole+ @BookBranchSt+ "Book"+ ''BookRole+ ''BookBranchSt
test/PingPong.hs view
@@ -28,9 +28,9 @@ import Data.Kind import GHC.Exts (dataToTag#) import GHC.Int (Int (I#))-import TypedProtocol.Codec-import TypedProtocol.Core-import TypedProtocol.Driver+import TypedSession.Codec+import TypedSession.Core+import TypedSession.Driver {-
typed-session.cabal view
@@ -20,7 +20,7 @@ -- PVP summary: +-+------- breaking API changes -- | | +----- non-breaking API additions -- | | | +--- code changes with no API change-version: 0.1.0.0+version: 0.1.1.0 -- A short (one-line) description of the package. synopsis: typed session framework@@ -63,9 +63,10 @@ -- Modules exported by the library. exposed-modules: Data.IFunctor- , TypedProtocol.Core- , TypedProtocol.Codec- , TypedProtocol.Driver+ , TypedSession.Core+ , TypedSession.Codec+ , TypedSession.Driver+ , TypedSession.TH -- Modules included in this library but not exported. -- other-modules:@@ -77,6 +78,8 @@ build-depends: base >= 4.20.0 && < 4.21 , containers >= 0.7 && < 0.8 , io-classes >= 1.5.0 && < 1.6+ , template-haskell+ , typed-session-state-algorithm -- Directories containing source files. hs-source-dirs: src@@ -96,6 +99,7 @@ , Book1 , Book2 , Book3.Type+ , Book3.Protocol , Book3.Peer , Book3.Main , PingPong@@ -121,7 +125,8 @@ io-sim, random, fused-effects,- fused-effects-random+ fused-effects-random,+ template-haskell ghc-options: -Werror=inaccessible-code -threaded source-repository head