Experience reports using indexed monads in production?

前端 未结 3 1394
感情败类
感情败类 2021-02-08 17:18

In a previous question I discovered the existence of Conor McBride\'s Kleisli arrows of Outrageous Fortune while looking for ways of encoding Idris examples in Haskell. My effor

3条回答
  •  余生分开走
    2021-02-08 17:56

    Session types are an attempt to give type-level descriptions to networking protocols. The idea is that if a client sends a value, the server must be ready to receive it, and vice versa.

    So here's a type (using TypeInType) describing sessions consisting of a sequence of values to send and values to receive.

    infixr 5 :!, :?
    data Session = Type :! Session
                 | Type :? Session
                 | E
    

    a :! s means "send a value of type a, then continue with the protocol s". a :? s means "receive a value of type a, then continue with the protocol s".

    So Session represents a (type-level) list of actions. Our monadic computations will work their way along this list, sending and receiving data as the type demands it. More concretely, a computation of type Chan s t a reduces the remaining work to be done to satisfy the protocol from s to t. I'll build Chan using the indexed free monad that I used in my answer to your other question.

    class IFunctor f where
        imap :: (a -> b) -> f i j a -> f i j b
    class IFunctor m => IMonad m where
        ireturn :: a -> m i i a
        (>>>=) :: m i j a -> (a -> m j k b) -> m i k b
    
    
    data IFree f i j a where
        IReturn :: a -> IFree f i i a
        IFree :: f i j (IFree f j k a) -> IFree f i k a
    
    instance IFunctor f => IFunctor (IFree f) where
        imap f (IReturn x) = IReturn (f x)
        imap f (IFree fx) = IFree (imap (imap f) fx)
    
    instance IFunctor f => IMonad (IFree f) where
        ireturn = IReturn
        IReturn x >>>= f = f x
        IFree fx >>>= f = IFree (imap (>>>= f) fx)
    

    Our base actions in the Chan monad will simply send and receive values.

    data ChanF s t r where
        Send :: a -> r -> ChanF (a :! s) s r
        Recv :: (a -> r) -> ChanF (a :? s) s r
    
    instance IFunctor ChanF where
        imap f (Send x r) = Send x (f r)
        imap f (Recv r) = Recv (fmap f r)
    
    send :: a -> Chan (a :! s) s ()
    send x = IFree (Send x (IReturn ()))
    
    recv :: Chan (a :? s) s a
    recv = IFree (Recv IReturn)
    
    type Chan = IFree ChanF
    type Chan' s = Chan s E  -- a "complete" Chan
    

    send takes the current state of the session from a :! s to s, fulfilling the obligation to send an a. Likewise, recv transforms a session from a :? s to s.

    Here's the fun part. When one end of the protocol sends a value, the other end must be ready to receive it, and vice versa. This leads to the idea of a session type's dual:

    type family Dual s where
        Dual (a :! s) = a :? Dual s
        Dual (a :? s) = a :! Dual s
        Dual E = E
    

    In a total language Dual (Dual s) = s would be provable, but alas Haskell is not total.

    You can connect a pair of channels if their types are dual. (I guess you'd call this an in-memory simulation of connecting a client and a server.)

    connect :: Chan' s a -> Chan' (Dual s) b -> (a, b)
    connect (IReturn x) (IReturn y) = (x, y)
    connect (IReturn _) (IFree y) = case y of {}
    connect (IFree (Send x r)) (IFree (Recv f)) = connect r (f x)
    connect (IFree (Recv f)) (IFree (Send y r)) = connect (f y) r
    

    For example, here's a protocol for a server which tests whether a number is greater than 3. The server waits to receive an Int, sends back a Bool, and then ends the computation.

    type MyProtocol = Int :? Bool :! E
    
    server :: Chan' MyProtocol ()
    server = do  -- using RebindableSyntax
        x <- recv
        send (x > 3)
    
    client :: Chan' (Dual MyProtocol) Bool
    client = do
        send 5
        recv
    

    And to test it:

    ghci> connect server client
    ((),True)
    

    Session types are an area of active research. This particular implementation is fine for very simple protocols, but you can't describe protocols where the type of the data being sent over the wire depends on the state of the protocol. For that you need, surprise surprise, dependent types. See this talk by Brady for a quick demo of the state of the art of session types.

提交回复
热议问题