\section{Session Types in Haskell} \label{sec:session} \ignore{ > {-# LANGUAGE TypeOperators, > EmptyDataDecls, > MultiParamTypeClasses, > FunctionalDependencies, > UndecidableInstances #-} > module Control.Concurrent.SimpleSession.SessionTypes ( > Z, S, > Eps, (:!:), (:?:), (:+:), (:&:), Rec, Var, > Dual > ) where > > infixr 3 :!:, :?: > infix 2 :+:, :&: > > data Z > data S n } The central idea of session types \citep{r:gay99} is to parameterize a channel with some type that represents a protocol, which the type system then enforces. In Haskell, we may encode a protocol using ordinary datatypes: > data (:!:) a r > data (:?:) a r > data Eps These datatypes require no constructors because they will have no run-time representation. If |a| is any type, and |r| is a protocol, then we interpret |a :!: r| as the protocol, ``first send an |a|, and then continue with |r|.'' Similarly, we interpret |a :?: r| as the protocol, ``receive an |a|, and then continue with |r|.'' The type |Eps| represents the empty protocol of a depleted channel that is not yet closed. For example, the type |Int :!: Bool :?: Eps| represents the protocol, ``send an |Int|, receive a |Bool|, and close the channel.''\thinspace\footnote{The type constructors |(:!:)| and |(:?:)| are declared right associative and with higher precedence than |(:+:)| and |(:&:)|.} If the process on one end of a channel speaks a particular protocol, its correspondant at the other end of the channel must be prepared to understand it. For example, if one process speaks |Int :!: Bool :?: Eps|, the other process must implement the dual protocol |Int :?: Bool :!: Eps|. We encode the duality relation using a type class with multiple parameters and functional dependencies \citep{Jones1997Type,Jones2000Type}. > class Dual r s | r -> s, s -> r The functional dependencies indicate that duality is bijective, which helps Haskell to infer protocols and enables a form of subtyping. Sending and receiving are dual: if |r| is dual to |s|, then |a :!: r| is dual to |a :?: s|. The empty session is dual to itself. > instance Dual r s => Dual (a :!: r) (a :?: s) > instance Dual r s => Dual (a :?: r) (a :!: s) > instance Dual Eps Eps \par Our session types also represent alternation and recursion. If |r| and |s| are protocols, then |r :+: s| represents an active choice between following |r| or |s|. The type |r :&: s| represents an offer to follow either |r| or |s|, as chosen by the other process. > data (:+:) r s > data (:&:) r s The two alternation operators are dual: > instance (Dual r1 s1, Dual r2 s2) => > Dual (r1 :+: r2) (s1 :&: s2) > instance (Dual r1 s1, Dual r2 s2) => > Dual (r1 :&: r2) (s1 :+: s2) \par Recursion turns out to be slightly more difficult. It is tempting to use a fixed-point combinator, but this would require constructing a type of kind $\star \to \star$ for any desired loop body, which is not generally possible. We need some other way for a recursive type to refer to itself, so we represent this binding using de~Bruijn indices. > data Rec r > data Var v > > instance Dual r s => Dual (Rec r) (Rec s) > instance Dual (Var v) (Var v) The type |Rec r| adds a binding for |r| inside |r|; that is, it implicitly defines a variable bound to the whole of |r| that can be used \emph{within} |r|. We use |Var v| to refer to the variable bound by the |v|th |Rec|, counting outward, where |v| is a Peano numeral written with type constructors |Z| and |S| (\emph{e.g.,} |Z| or |S (S Z)|). For example, the protocol < Request :!: Rec (Response :?: (Var Z :&: Eps)) says to send a request and then be prepared to receive one or more responses. By contrast, a process implementing the protocol < Request :!: Rec ((Response :?: Var Z) :&: Eps) must send a request and be prepared to accept any number of responses.