Thomas Dietert
Haskell Exchange
October 13th, 2018
Intro to \(\pi\)-calculus
Static Analysis
Structural Congruence
Bisimilarity
Static & Dynamic Semantics
Implementation in Haskell
Encoding...
data as processes
the lambda calculus
Names, in depth
Static Analysis Implementations
Concurrent Evaluation
Distributed Systems
cloud haskell
a model of the changing connectivity of interactive systems
an open calculus with respect to names
$$ \begin{aligned} \text{names:} \quad \forall x & \in \mathcal{N} \\ \text{co-names:} \quad \forall \overline{x} & \in \overline{\mathcal{N}} \end{aligned}$$
names can be channels or variables.
Variables are implementation dependent.
... change the semantics of my program?
In a summand \(\pi.P\) the prefix \(\pi\) represents an atomic action, the first action performed by \(\pi.P\). There are two basic forms of \(\pi\) :
\(|\; P + Q \)
but summation is often elided to simplify static analysis such as structural congruence, bisimilarity, and evalution.
$$ x(y).0 \implies x(y) \\ \overline{x}\langle y \rangle .0 \implies \overline{x}\langle y \rangle $$
Null processes are often omitted at the end of process expressions.
Processes \( P \) and \( Q \) are structurally congruent, written \( \equiv \), if we can transform one into the other via a finite sequence steps that preserve the semantics of the process expression.
Change of bound names (\(\alpha\)-equivalence)
\( \begin{aligned} P \mid 0 & \equiv P \\ P \mid Q & \equiv Q \mid P \\ P \mid ( Q \mid R ) & \equiv (P \mid Q) \mid R \end{aligned}\)
\( \begin{aligned} & (\nu x) (P \mid Q) \equiv P \mid (\nu x)Q \quad\quad\quad\quad if x \not\in \text{fn}(P) \\ & (\nu x) 0 \equiv 0, \ (\nu x)(\nu y) P \equiv (\nu y)(\nu x) P \end{aligned} \)
\( !P \equiv P \mid !P \)
(1)
(2)
(3)
(4)
Prove that \((\nu a)(Q \mid P) \equiv P \mid Q \)
if \(a \not\in \ \text{fn}(P) \cup \text{fn}(Q) \)
$$ \equiv (Q \mid P) \mid (\nu a)\, 0 $$
$$ \equiv (Q \mid P) \mid 0 $$
$$ \equiv Q \mid P $$
$$ \equiv P \mid Q $$
$$ (2) $$
$$ (3) $$
$$ (3) $$
$$ (2) $$
$$ (2) $$
\( \begin{aligned} P \mid 0 & \equiv P \\ P \mid Q & \equiv Q \mid P \\ P \mid ( Q \mid R ) & \equiv (P \mid Q) \mid R \end{aligned}\)
\( \begin{aligned} & (\nu x) (P \mid Q) \equiv P \mid (\nu x)\,Q \quad if x \not\in \text{fn}(P) \\ & (\nu x) \,0 \equiv 0, \ (\nu x)(\nu y) P \equiv (\nu y)(\nu x) P \end{aligned} \)
$$(2)$$
$$(3)$$
$$ \begin{aligned} \text{fn}(0) \ & = \ \emptyset \\ \text{fn}(\overline{x}\langle y \rangle .P) \ & = \ \{x,y\} \cup \text{fn}(P) \\ \text{fn}((\nu x)P) \ & = \ \text{fn}(P) \setminus \{x\} \end{aligned} $$
$$ \begin{aligned} \text{fn}(P \mid Q) \ & = \ \text{fn}(P) \cup \text{fn}(Q) \\ \text{fn}(x(y).P) \ & = \ (\text{fn}(P) \setminus \{y\}) \cup \{x\} \\ \text{fn}(!P) \ & = \ \text{fn}(P) \end{aligned} $$
$$\begin{aligned} * \quad \text{bn}(\overline{x}\langle y \rangle) \ & = \ \emptyset \\ \text{bn}(x(y)) \ & = \ \{y\} \end{aligned}$$
$$ (*)$$
$$ (*)$$
$$\text{PAR}:$$
$$\text{RES}:$$
$$\text{COMM}:$$
$$\text{STRUCT}:$$
$$\prime$$
$$ P $$
$$ P^{\prime} $$
$$ Q $$
$$ $$
\(x(\widetilde{y}).P\)
\(\overline{x}\langle\widetilde{y}\rangle.P\)
$$ \begin{aligned} x(y,z).P \; \equiv & \;\;x(y).x(z).P \\ \overline{x}\langle y,z \rangle \; \equiv & \; \; \overline{x}\langle y \rangle . \overline{x} \langle z \rangle . P \end{aligned} $$
... maybe ?
$$ $$
$$ \equiv $$
$$ $$
$$ \mid \; x(\widetilde{y}).P $$
Receive on channel \(x\), bind the results to \(\widetilde{y}\), then run \(P\)
Send the values \(\widetilde{y}\) over channel \(x\), then run \(P\)
$$ \mid \; \overline{x}\langle \widetilde{y} \rangle .P $$
$$ x(\widetilde{y}).P \mid \overline{x}\langle \widetilde{z} \rangle.Q \rightarrow P \mid \{\widetilde{y} / \widetilde{z}\}Q$$
$$RES:$$
$$COMM:$$
$$STRUCT:$$
$$\text{PAR}:$$
$$\prime$$
$$ P $$
$$ P^{\prime} $$
$$ Q $$
Can process \(P\) simulate process \(Q\), and can process \(Q\) simulate process \(P\)?
A symmetric, binary relation \(\mathcal{S}\) on agents (processes) \(P\) and \(Q\), written \(P\mathcal{S}Q\), satisfying:
"if \(P\) can step to \(P\prime\) on some action \(\alpha\), then Q can do the same action, stepping to \(Q\prime\) such that \(P\prime\) and \(Q\prime\) are bisimilar."
$$ \tau.P $$
$$ \tau.P \xrightarrow{\tau} P $$
$$ \tau.P \mid Q \xrightarrow{\tau} P \mid Q $$
"Observational Equivalence"
$$ $$
$$ \begin{aligned} \delta \; ::= & \;\text{chan} \, [\delta_{1}, \cdots, \delta_{n}] \\ \mid & \; \; \tau \end{aligned}$$
$$ ... ? $$
$$ \Delta \; ::= \; \{ x_{1} : \delta_{1}, \cdots, x_{n} : \delta_{n} \} $$
$$ \Delta \vdash P \\ \Delta \vdash \; !P $$
$$ \Delta \vdash 0 $$
$$ \Delta(x) = \text{chan} \,[\delta_{1}, \ldots \delta_{n}] \quad \; \Delta, y_{1} : \delta_{1}, \ldots, y_{n} : \delta_{n} \vdash P \\ \Delta \vdash x(y_{1}, \ldots, y_{n}).P $$
$$ \Delta(x) = \text{chan} \, [\Delta(y_{1}), \ldots, \Delta(y_{n})] \quad \; \Delta \vdash P \\ \Delta \vdash \overline{x}\langle y_{1}, \ldots, y_{n}\rangle . P$$
$$ \text{T-PAR}:$$
$$ \text{T-NIL}:$$
$$\text{T-REPL}:$$
$$ \text{T-RES}:$$
$$ \text{T-INPUT}:$$
$$ \text{T-OUTPUT}:$$
It is well known that synchronous communication can be simulated using explicit acknowledgments in an asynchronous calculus
This restriction makes it significantly simpler to implement replication, since it becomes easy to detect when we need to create a new copy of the replicated process.
$$RES:$$
$$COMM:$$
$$STRUCT:$$
$$\text{PAR}:$$
$$\prime$$
$$ P $$
$$ P^{\prime} $$
$$ Q $$
$$ .P $$
$$ P \mid $$
$$\text{COMM-REPL-INP}:$$
$$ \Delta \vdash P \\ \Delta \vdash \; !P $$
$$ \Delta \vdash 0 $$
$$ \Delta(x) = \text{chan} \,[\delta_{1}, \ldots \delta_{n}] \quad \; \Delta, y_{1} : \delta_{1}, \ldots, y_{n} : \delta_{n} \vdash P \\ \Delta \vdash \, x(y_{1}, \ldots, y_{n}).P $$
$$ \Delta(x) = \text{chan} \, [\Delta(y_{1}), \ldots, \Delta(y_{n})] \quad \; \Delta \vdash P \\ \Delta \vdash \overline{x}\langle y_{1}, \ldots, y_{n}\rangle$$
$$ \text{T-PAR}:$$
$$ \text{T-NIL}:$$
$$\text{T-REPL}:$$
$$ \text{T-RES}:$$
$$ \text{T-INPUT}:$$
$$ \text{T-OUTPUT}:$$
$$ .P $$
$$ \Delta \vdash P $$
$$ ! $$
type PName = Name Proc
type Bindings p = Bind [PName] p
data Proc
= PNil -- ^ 0
| PName PName -- ^ x,y, or z
| PPar Proc Proc -- ^ P | Q
| POutput Proc [Proc] -- ^ x<y1..yN>
| PInput Proc (Bindings Proc) -- ^ x(z1..zN)
| PReplInput Proc (Bindings Proc) -- ^ !x(z1..zN)
| PRestrict (Bind (PName,TProc) Proc) -- ^ new x:t in P
deriving (Show, Generic)
package: unbound-generics
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE DeriveGeneric #-}
...
import Unbound.Generics.LocallyNameless
...
instance Alpha Proc
instance Subst Proc Proc where
isvar p =
case p of
PName nm -> Just (SubstName nm)
_ -> Nothing
data TProc
= TChan [TProc]
| TProc
deriving (Show, Generic)
instance Alpha TProc
instance Subst Proc TProc
$$ \tau $$
nil :: Proc
nil = PNil
var :: [Char] -> Proc
var x = PName (s2n x)
par :: Proc -> Proc -> Proc
par p q = PPar p q
output :: [Char] -> [[Char]] -> Proc
output x ys = POutput (var x) (map var ys)
input :: [Char] -> [[Char]] -> Proc -> Proc
input x ys p = PInput (var x) (bind (map s2n ys) p)
replinput :: [Char] -> [[Char]] -> Proc -> Proc
replinput x ys p = PReplInput (var x) (bind (map s2n ys) p)
res :: [Char] -> TProc -> Proc -> Proc
res x t p = PRestrict (bind (s2n x, t) p)
chan :: [TProc] -> TProc
chan = TChan
type TypeEnv = Map PName TProc
data TypeError
= TypeError Proc TProc TProc
| InvalidChanType Proc TProc
| UnboundVariable (Name Proc)
deriving (Show)
type TypecheckM = FreshMT (ExceptT TypeError (Reader TypeEnv))
runTypecheckM :: TypecheckM a -> Either TypeError a
runTypecheckM = flip runReader mempty . runExceptT . runFreshMT
lookupTypeEnv
:: PName
-- ^ Name of variable to look up
-> TypecheckM (Maybe TProc)
lookupTypeEnv pnm = do
env <- ask
pure (Map.lookup pnm env)
extendTypeEnv
:: NonEmpty (PName,TProc)
-- ^ List of variable names and types to add to the type environment
-> TypecheckM a
-- ^ Computation over which to extend the type environment
-> TypecheckM a
extendTypeEnv xts =
local (Map.union $ Map.fromList xts)
typecheck
:: Proc
-> TypecheckM TProc
typecheck p =
case p of
...
$$ \Delta \vdash 0 $$
$$ \text{T-NIL}:$$
typecheck
:: Proc
-> TypecheckM TProc
typecheck p =
case p of
PNil -> pure TProc
...
typecheck
:: Proc
-> TypecheckM TProc
typecheck p =
case p of
...
PName x -> do
mType <- lookupTypeEnv x
case mType of
Nothing -> throwError (UnboundVariable x)
Just t -> pure t
...
typecheck
:: Proc
-> TypecheckM TProc
typecheck p =
case p of
...
PPar p q -> do
typecheck p
typecheck q
pure TProc
...
$$ \text{T-PAR}:$$
typecheck
:: Proc
-> TypecheckM TProc
typecheck p =
case p of
...
POutput x ys -> do
xType <- typecheck x
ysTypes <- mapM typecheck ys
let xExpectedType = TChan ysTypes
if xType == xExpectedType
then pure TProc
else throwError (TypeError x xType xExpectedType)
...
$$ \Delta(x) = \text{chan} \, [\Delta(y_{1}), \ldots, \Delta(y_{n})] \\ \Delta \vdash \overline{x}\langle y_{1}, \ldots, y_{n}\rangle$$
$$ \text{T-OUTPUT}:$$
typecheck
:: Proc
-> TypecheckM TProc
typecheck p =
case p of
...
PInput x bys -> do
xType <- typecheck x
case xType of
TProc -> throwError (InvalidChanType x xType)
TChan ts -> do
(ys, p) <- unbind bys
extendTypeEnv (zip ys ts) (typecheck p)
...
$$ \Delta(x) = \text{chan} \,[\delta_{1}, \ldots \delta_{n}] \quad \; \Delta, y_{1} : \delta_{1}, \ldots, y_{n} : \delta_{n} \vdash P \\ \Delta \vdash x(y_{1}, \ldots, y_{n}).P $$
$$ \text{T-INPUT}:$$
typecheck
:: Proc
-> TypecheckM TProc
typecheck p =
case p of
...
PReplInput x bys -> do
xType <- typecheck x
case xType of
TProc -> throwError (InvalidChanType x xType)
TChan ts -> do
(ys, p) <- unbind bys
extendTypeEnv (zip ys ts) (typecheck p)
...
$$ \Delta(x) = \text{chan} \,[\delta_{1}, \ldots \delta_{n}] \quad \; \Delta, y_{1} : \delta_{1}, \ldots, y_{n} : \delta_{n} \vdash P \\ \Delta \vdash \; !x(y_{1}, \ldots, y_{n}).P $$
$$ \text{T-REPL-INP}:$$
typecheck
:: Proc
-> TypecheckM TProc
typecheck p =
case p of
...
PRestrict bxp -> do
((x,t),p) <- unbind bxp
case t of
TProc -> throwError (InvalidChanType (PName x) t)
TChan _ -> extendTypeEnv [(x,t)] (typecheck p)
$$ \text{T-RES}:$$
$$ $$
$$ \Delta, x : \delta \vdash P \\ \Delta \vdash (\nu \, x : \delta) P$$
type inference left as an exercise for the reader...
...
It is much more important for a \(\pi\)-calculus abstract machine to provide fair execution, guaranteeing that runnable processes will eventually be executed, and that processes waiting to communicate on a channel will eventually succeed (if sufficient communication partners become available).
- The Polyadic \(\pi\)-calculus, p. 99-100
data ChanQueueElem
= Reader (Bindings Proc)
| Writer [Proc]
| ReplReader (Bindings Proc)
type ChanQueue = Seq ChanQueueElem
type Heap = Map PName ChanQueue
type RunQueue = Seq Proc
data MachineState = MachineState
{ evalHeap :: Heap
, evalRunQueue :: RunQueue
}
data ReductionRule
= RuleNil
| RulePrl
| RuleRes
| RuleInpW
| RuleInpR
| RuleOutW
| RuleOutR
| RuleOutRStar
| RuleReplW
| RuleReplR
deriving (Show)
data ReductionStep =
ReductionStep ReductionRule MachineState
deriving (Show)
type EvalM a =
FreshMT
(StateT MachineState
(WriterT [ReductionStep]
(Except EvalError
)
)
) a
pushRunQueue :: Proc -> EvalM ()
enqueueRunQueue :: Proc -> EvalM ()
dequeueRunQueue :: EvalM (Maybe Proc)
insertHeap :: PName -> ChanQueue -> EvalM ()
lookupHeap :: PName -> EvalM ChanQueue
peekChanQueue :: PName -> EvalM ChanQueueElem
dequeueChanQueue :: PName -> EvalM ChanQueueElem
enqueueChanQueue :: PName -> ChanQueueElem -> EvalM ()
insertHeap :: PName -> ChanQueue -> EvalM ()
insertHeap pnm cq =
modify $ \evalState ->
evalState { evalHeap = Map.insert pnm cq (evalHeap evalState) }
runEvalM :: MachineState -> EvalM () -> Either EvalError [ReductionStep]
runEvalM machineState =
fmap snd . runExcept . runWriterT . flip evalStateT machineState . runFreshMT
runMachine :: Proc -> Either EvalError [ReductionStep]
runMachine initProc =
runEvalM (initMachineState initProc) machineLoop
where
machineLoop = do
mProc <- dequeueRunQueue
case mProc of
-- If no processes in run queue, terminate.
Nothing -> pure ()
-- Otherwise, evaluate the process at the head of the run queue
Just p -> eval p >> machineLoop
-- | Evaluate a process at the head of the run queue,
-- returning the reduction rule used to evaluate the
-- given process expression.
eval :: Proc -> EvalM ()
eval proc = do
rule <-
case proc of
{- Reduction step here... -}
-- Emit the reduction rule and resulting machine state
machineState <- get
tell [ReductionStep rule machineState]
eval :: Proc -> EvalM ()
eval proc = do
rule <-
case proc of
PNil -> pure RuleNil
...
...
eval :: Proc -> EvalM ()
eval proc = do
rule <-
case proc of
...
PName pnm -> panic "Trying to eval a name..."
...
...
eval :: Proc -> EvalM ()
eval proc = do
rule <-
case proc of
...
PPar p q -> do
pushRunQueue p
enqueueRunQueue q
pure RulePrl
...
...
eval :: Proc -> EvalM ()
eval proc = do
rule <-
case proc of
...
PRestrict bxp -> do
((x,_),p) <- unbind bxp
insertHeap x Empty
pushRunQueue p
pure RuleRes
...
...
eval :: Proc -> EvalM ()
eval proc = do
rule <-
case proc of
...
PInput (PName c) bxs -> do
cq <- lookupHeap c
case cq of
Writer pas :<| ws -> do
(xs, p) <- unbind bxs
let as = map pName pas
pxs = map PName xs
p' = substs (zip as pxs) p
insertHeap c ws
pushRunQueue p'
pure RuleInpW
...
...
...
eval :: Proc -> EvalM ()
eval proc = do
rule <-
case proc of
...
PInput (PName c) bxs -> do
cq <- lookupHeap c
case cq of
...
rs -> do
insertHeap c (rs |> Reader bxs)
pure RuleInpR
...
...
eval :: Proc -> EvalM ()
eval proc = do
rule <-
case proc of
...
POutput (PName c) pas -> do
cq <- lookupHeap c
case cq of
Reader bxs :<| rs -> do
(xs, q) <- unbind bxs
insertHeap c rs
let as = map pName pas
pxs = map PName xs
q' = substs (zip as pxs) q
enqueueRunQueue q'
pure RuleOutR
...
...
...
eval :: Proc -> EvalM ()
eval proc = do
rule <-
case proc of
...
POutput (PName c) pas -> do
cq <- lookupHeap c
case cq of
...
rr@(ReplReader bxs) :<| rs -> do
(xs, q) <- unbind bxs
insertHeap c (rs |> rr)
let as = map pName pas
pxs = map PName xs
q' = substs (zip as pxs) q
enqueueRunQueue q'
pure RuleOutRStar
...
...
...
eval :: Proc -> EvalM ()
eval proc = do
rule <-
case proc of
...
POutput (PName c) pas -> do
cq <- lookupHeap c
case cq of
...
ws -> do
insertHeap c (ws |> Writer pas)
pure RuleOutW
...
...
eval :: Proc -> EvalM ()
eval proc = do
rule <-
case proc of
...
PReplInput (PName c) bxs -> do
cq <- lookupHeap c
case cq of
Writer pas :<| ws -> do
(xs, p) <- unbind bxs
insertHeap c ws
pushRunQueue proc
let as = map pName pas
pxs = map PName xs
p' = substs (zip as pxs) p
enqueueRunQueue p'
pure RuleReplW
...
...
...
eval :: Proc -> EvalM ()
eval proc = do
rule <-
case proc of
...
PReplInput (PName c) bxs -> do
cq <- lookupHeap c
case cq of
...
rs -> do
insertHeap c (rs |> ReplReader bxs)
pure RuleReplR
...
...
exampleReduction :: Proc
exampleReduction =
res "x" (chan [])
(par
(output "x" [])
(replinput "x" [] nil)
)
data ProcError
= ProcTypeError TypeError
| ProcEvalError EvalError
deriving (Show)
process :: Proc -> Either ProcError [ReductionStep]
process p = do
first ProcTypeError (runTypecheckM (typecheck p))
first ProcEvalError (runMachine p)
runExampleReduction :: IO ()
runExampleReduction = do
case process exampleReduction of
Left err -> panic (show err)
Right reds -> mapM_ print reds
[2018-10-11 21:33:24] thomas@bonobo:~/github/pi-calculus $ stack ghci src/PolyadicAsync.hs
Using configuration for pi-calculus:lib to load /home/thomas/github/pi-calculus/src/PolyadicAsync.hs
Configuring GHCi with the following packages: pi-calculus
GHCi, version 8.2.2: http://www.haskell.org/ghc/ :? for help
Loaded GHCi configuration from /home/thomas/.ghci
[1 of 1] Compiling PolyadicAsync ( /home/thomas/github/pi-calculus/src/PolyadicAsync.hs, interpreted )
Ok, one module loaded.
Loaded GHCi configuration from /tmp/haskell-stack-ghci/820cd9a9/ghci-script
*PolyadicAsync λ> runExampleReduction
ReductionStep RuleRes
(MachineState {
evalHeap = fromList [(x,fromList [])]
, evalRunQueue = fromList [PPar (POutput (PName {pName = x}) []) (PReplInput (PName {pName = x}) (<[]> PNil))]
})
ReductionStep RulePrl
(MachineState {
evalHeap = fromList [(x,fromList [])]
, evalRunQueue = fromList [POutput (PName {pName = x}) [], PReplInput (PName {pName = x}) (<[]> PNil)]
})
ReductionStep RuleOutW
(MachineState {
evalHeap = fromList [(x,fromList [Writer []])]
, evalRunQueue = fromList [PReplInput (PName {pName = x}) (<[]> PNil)]
})
ReductionStep RuleReplW
(MachineState {
evalHeap = fromList [(x,fromList [])]
, evalRunQueue = fromList [PReplInput (PName {pName = x}) (<[]> PNil),PNil]
})
ReductionStep RuleReplR
(MachineState {
evalHeap = fromList [(x,fromList [ReplReader (<[]> PNil)])]
, evalRunQueue = fromList [PNil]
})
ReductionStep RuleNil
(MachineState {
evalHeap = fromList [(x,fromList [ReplReader (<[]> PNil)])]
, evalRunQueue = fromList []
})
https://github.com/tdietert/pi-calculus