Formalizing Concurrent Systems: The \(\pi\)-calculus
... in Haskell!
Thomas Dietert
Haskell Exchange
October 13th, 2018
whoami
- Software Engineer @adjoint_io
- Distributed systems
- PL design & implementation
- Programming Language & Type Theory enthusiast
- Reader, boulderer, and American IPA drinker...
Overview
-
Intro to \(\pi\)-calculus
-
Static Analysis
-
Structural Congruence
-
Bisimilarity
-
-
Static & Dynamic Semantics
-
Implementation in Haskell
Topics not
covered
-
Encoding...
-
data as processes
-
the lambda calculus
-
-
Names, in depth
-
Static Analysis Implementations
-
Concurrent Evaluation
-
Distributed Systems
-
cloud haskell
-
\(\pi\)
calculus:
a particular method or system of calculation or reasoning.
\(\pi\)(process)-calculus
a model of the changing connectivity of interactive systems
- models networking in the broad, modern sense
- can be seen as a basic model of computation
- rests upon the primitive notion of interaction
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.
But why... ?
Reasoning about distributed systems is hard.
concurrent
- Did my "bug-fix"...
- Did my "optimization"...
Are these two programs equivalent?
Static Analyses
... change the semantics of my program?
more formally put,
\(\pi\)-calculus gives us...
Operational Congruences:
- when do two processes have the same behavior?
- when does a process meet a specification?
- when is an abstract machine (evaluator) correct?
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\) :
Monadic \(\pi\)-calculus
Monadic \(\pi\)-calculus
\(|\; P + Q \)
but summation is often elided to simplify static analysis such as structural congruence, bisimilarity, and evalution.
some syntactic sugar...
$$ 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.
Structural Congruence
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.
Structural Congruence
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}$$
$$ (*)$$
$$ (*)$$
Free/Bound Names
Reduction Rules
$$\text{PAR}:$$
$$\text{RES}:$$
$$\text{COMM}:$$
$$\text{STRUCT}:$$
$$\prime$$
$$ P $$
$$ P^{\prime} $$
$$ Q $$
Example Reduction
$$ $$
\(x(\widetilde{y}).P\)
\(\overline{x}\langle\widetilde{y}\rangle.P\)
$$\implies$$
$$\implies$$
Polyadic \(\pi\)-calculus
more syntactic sugar...
$$ \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 $$
The correct conversion
$$ $$
Polyadic \(\pi\)-calculus
$$ \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 $$
Reduction Rules pt. 2
$$ 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 $$
Bisimilarity
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 $$
Weak Bisimilarity
"Observational Equivalence"
Strong Bisimilarity
A Simple Type System
$$ $$
$$ \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}:$$
Polyadic, Asynchronous
\(\pi\)-calculus
It is well known that synchronous communication can be simulated using explicit acknowledgments in an asynchronous calculus
Simulating Synchronicity:
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.
Syntax
Reduction Rules
$$RES:$$
$$COMM:$$
$$STRUCT:$$
$$\text{PAR}:$$
$$\prime$$
$$ P $$
$$ P^{\prime} $$
$$ Q $$
$$ .P $$
$$ P \mid $$
$$\text{COMM-REPL-INP}:$$
Typing Rules
$$ \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 $$
$$ ! $$
Implementation
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)
Syntax
CAS (Capture Avoiding Substitution)
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
Types
data TProc
= TChan [TProc]
| TProc
deriving (Show, Generic)
instance Alpha TProc
instance Subst Proc TProc
$$ \tau $$
a small DSL
... almost
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
Typechecking
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)
a few helpers...
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
...
those names tho...
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...
Evaluation
An Abstract Machine
...
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)
Reduction Rules
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 ()
Evaluation Monad
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
...
...
those names tho...
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
...
...
Example Reduction
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
expected reductions:
[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
References
communicating
and mobile
systems: the
- calculus
Robin Milner
\(\pi\)
The Polymorphic Pi-Calculus:
Theory and Implementation
- David N. Turner
Applied \(\pi\ -\) A Brief Tutorial
- Peter Sewell
An Introduction to the \(\pi\)-calculus
- Joachim Parrow
Further Reading
- Pict: The Programming Language
- Distributed \(\pi\)-calculus
- Cryptographic Protocols:
- Applied \(\pi\)-calculus
- Spi-calculus
- Time... and space?
Questions?
deck
By Thomas Dietert
deck
- 554