385 lines
15 KiB
Scheme
385 lines
15 KiB
Scheme
#lang scheme
|
|
|
|
;; this is a formulation of the pi-calculus in redex, following Milner's 1990 paper,
|
|
;; "Functions as Processes", available online (for now, anyhow) at:
|
|
;; ftp://ftp.inria.fr/INRIA/publication/publi-ps-gz/RR/RR-1154.ps.gz
|
|
|
|
|
|
;; the challenge here is handling the structural congruence rules; in particular,
|
|
;; these rules use congruence "as needed" to make the reductions work. An alternate
|
|
;; solution would be to determine a canonical representative for each of the
|
|
;; equivalence classes implied by structural congruence, and to formulate reduction
|
|
;; rules that are guaranteed to converge to this canonical element. This would
|
|
;; have the advantage that you wouldn't have to trust that my "as needed" rules
|
|
;; are adequate, but ... I couldn't see how to do it cleanly in the presence of
|
|
;; replicating terms. Also, this solution has the advantage that its steps match
|
|
;; those of milner; each of his steps is exactly one of our steps.
|
|
|
|
;; this model performs no "garbage collection" to remove dead terms.
|
|
|
|
(require redex/reduction-semantics)
|
|
|
|
(define-language π-calculus
|
|
(π (π π) ;; parallel composition
|
|
zero ;; dead computation
|
|
(nu a π) ;; fresh name
|
|
(out a a π) ;; sending
|
|
(in a a π) ;; receiving
|
|
(! π)) ;; replication
|
|
(a variable)
|
|
;; evaluation contexts:
|
|
(E hole
|
|
(E π)
|
|
(π E)
|
|
(nu a E)
|
|
(! E)))
|
|
|
|
|
|
(define red
|
|
(reduction-relation
|
|
π-calculus
|
|
|
|
;; the only reduction rule: an "in" matches with an "out" on the same channel.
|
|
(--> (in-hole E_1 ((in-hole E_in (in a_ch a_x π_in)) (in-hole E_out (out a_ch a_o π_out))))
|
|
(in-hole E_1 (maybe-lift sent E_in (in a_ch a_x π_in) E_out (out a_ch a_o π_out)))
|
|
|
|
(fresh sent)
|
|
;; the name of the channel must be free on both sides:
|
|
(side-condition (and (not (member (term a_ch) (term (bound-by E_in))))
|
|
(not (member (term a_ch) (term (bound-by E_out)))))))
|
|
|
|
|
|
;; EXACTLY THE SAME except in & out are reversed in the input:
|
|
(--> (in-hole E_1 ((in-hole E_out (out a_ch a_o π_out)) (in-hole E_in (in a_ch a_x π_in))))
|
|
(in-hole E_1 (maybe-lift sent E_in (in a_ch a_x π_in) E_out (out a_ch a_o π_out)))
|
|
|
|
(fresh sent)
|
|
;; the name of the channel must be free on both sides:
|
|
(side-condition (and (not (member (term a_ch) (term (bound-by E_in))))
|
|
(not (member (term a_ch) (term (bound-by E_out)))))))))
|
|
|
|
|
|
;; maybe-lift : given a fresh identifier and two context/π pairs, perform replication
|
|
;; on each pair if necessary and then lift the nu-binding of the a_o name if necessary
|
|
;; (renaming it a_lifted)
|
|
(define-metafunction π-calculus
|
|
maybe-lift : a E π E π -> π
|
|
|
|
;; the name being sent needs lifting, so that it includes the recipient:
|
|
[(maybe-lift a_lifted E_in (in a_ch a_x π_in) E_out (out a_ch a_o π_out))
|
|
(nu a_lifted ((in-hole E_i¬! (subst a_x a_lifted π_in))
|
|
(in-hole (rename-innermost a_o a_lifted E_o¬!)
|
|
(subst a_o a_lifted π_out))))
|
|
|
|
(where E_i¬! (replicate-as-needed E_in (in a_ch a_x π_in)))
|
|
(where E_o¬! (replicate-as-needed E_out (out a_ch a_o π_out)))
|
|
|
|
(side-condition (member (term a_o) (term (bound-by E_out))))]
|
|
|
|
;; otherwise, no need to lift nu:
|
|
[(maybe-lift a_lifted E_in (in a_ch a_x π_in) E_out (out a_ch a_o π_out))
|
|
((in-hole E_i¬! (subst a_x a_o π_in))
|
|
(in-hole E_o¬! π_out))
|
|
|
|
(where E_i¬! (replicate-as-needed E_in (in a_ch a_x π_in)))
|
|
(where E_o¬! (replicate-as-needed E_out (out a_ch a_o π_out)))])
|
|
|
|
|
|
|
|
;; replicate-as-needed : performs replication to uncover the desired
|
|
;; expression
|
|
(define-metafunction π-calculus
|
|
replicate-as-needed : E π -> E
|
|
[(replicate-as-needed hole π_orig) hole]
|
|
[(replicate-as-needed (E π) π_orig) ((replicate-as-needed E π_orig) π)]
|
|
[(replicate-as-needed (π E) π_orig) (π (replicate-as-needed E π_orig))]
|
|
[(replicate-as-needed (nu a E) π_orig) (nu a (replicate-as-needed E π_orig))]
|
|
[(replicate-as-needed (! E) π_orig) ((replicate-as-needed E π_orig)
|
|
(in-hole (! E) π_orig))])
|
|
|
|
;; rename-innermost : rename the *innermost* binding of a given name along
|
|
;; an evaluation context, using another given name. At this point,
|
|
;; there should be no bangs along the context. Also, a_from should
|
|
;; be fresh, so there's no need to worry about those collisions.
|
|
(define-metafunction π-calculus
|
|
rename-innermost : a_from a_to E -> E
|
|
;; no hole case; the base case is no more bindings of a_from
|
|
[(rename-innermost a_from a_to (E π)) ((rename-innermost a_from a_to E) π)]
|
|
[(rename-innermost a_from a_to (π E)) (π (rename-innermost a_from a_to E))]
|
|
|
|
;; this is the last! substitute from here on out:
|
|
[(rename-innermost a_from a_to (nu a_from E)) (subst/E a_from a_to E)
|
|
(side-condition (not (member (term a_from) (term (bound-by E)))))]
|
|
|
|
[(rename-innermost a_from a_to (nu a_other E)) (nu a_other (rename-innermost a_from a_to E))]
|
|
;; no case for bang.
|
|
)
|
|
|
|
;; THE REST OF THIS IS BOILERPLATE... (subst, free-vars, etc.)
|
|
|
|
|
|
;; BOUND-BY : compute the variables bound along the sπne of a context
|
|
(define-metafunction π-calculus
|
|
bound-by : E -> (a ...)
|
|
[(bound-by hole) ()]
|
|
[(bound-by (E π)) (bound-by E)]
|
|
[(bound-by (π E)) (bound-by E)]
|
|
[(bound-by (nu a E)) (a a_rest ...)
|
|
(where (a_rest ...) (bound-by E))]
|
|
[(bound-by (! E)) (bound-by E)])
|
|
|
|
|
|
|
|
|
|
;; SUBST
|
|
|
|
;; substitute a_to for a_from in π. Note that renaming may be necessary,
|
|
;; when looking inside a binder for a_to.
|
|
(define-metafunction π-calculus
|
|
subst : a a π -> π
|
|
[(subst a_from a_to zero) zero]
|
|
[(subst a_from a_to (π_1 π_2)) ((subst a_from a_to π_1)
|
|
(subst a_from a_to π_2))]
|
|
;; shadowing of a_from: stop substitution
|
|
[(subst a_from a_to (nu a_from π)) (nu a_from π)]
|
|
;; shadowing of a_to: rename if a_from occurs free.
|
|
[(subst a_from a_to (nu a_to π))
|
|
(nu a_new (subst a_from a_to (subst a_to a_new π)))
|
|
(where a_new (fresh-in (nu a_to π) a_to))
|
|
(side-condition (member (term a_from) (term (free-vars π))))]
|
|
;; otherwise, proceed:
|
|
[(subst a_from a_to (nu a π))
|
|
(nu a (subst a_from a_to π))]
|
|
|
|
[(subst a_from a_to (out a_1 a_2 π))
|
|
(out (subst-name a_from a_to a_1)
|
|
(subst-name a_from a_to a_2)
|
|
(subst a_from a_to π))]
|
|
;; shadowing of a_from: stop substitution
|
|
[(subst a_from a_to (in a_ch a_from π))
|
|
(in (subst-name a_from a_to a_ch) a_from π)]
|
|
;; shadowing of a_to: rename if a_from occurs free
|
|
[(subst a_from a_to (in a_ch a_to π))
|
|
(in (subst-name a_from a_to a_ch)
|
|
a_new
|
|
(subst a_from a_to (subst a_to a_new π)))
|
|
(where a_new (fresh-in (in a_ch a_to π) a_to))
|
|
(side-condition (member (term a_from) (term (free-vars π))))]
|
|
;; otherwise, proceed:
|
|
[(subst a_from a_to (in a_ch a_x π))
|
|
(in (subst-name a_from a_to a_ch) a_x (subst a_from a_to π))]
|
|
|
|
[(subst a_from a_to (! π))
|
|
(! (subst a_from a_to π))])
|
|
|
|
;; SUBST/E : substitute a_to for a_from in π; assumes
|
|
;; there are no more bindings for a_from along the context sπne,
|
|
;; and that a_to is fresh (enough).
|
|
(define-metafunction π-calculus
|
|
subst/E : a_from a_to E -> E
|
|
|
|
[(subst/E a_from a_to hole) hole]
|
|
[(subst/E a_from a_to (π E)) ((subst a_from a_to π) (subst/E a_from a_to E))]
|
|
[(subst/E a_from a_to (E π)) ((subst/E a_from a_to E) (subst a_from a_to π))]
|
|
[(subst/E a_from a_to (nu a E)) (nu a (subst/E a_from a_to E))]
|
|
[(subst/E a_from a_to (! E)) (! (subst/E a_from a_to))])
|
|
|
|
|
|
;; SUBST-NAME: replace a_from with a_to, leave other names alone.
|
|
(define-metafunction π-calculus
|
|
subst-name : a a a -> a
|
|
[(subst-name a_src a_tgt a_src) a_tgt]
|
|
[(subst-name a_src a_tgt a) a])
|
|
|
|
;; FREE-VARS : return a list of the variables that occur free in the term
|
|
;; WARNING: MAY INCLUDE DUPLICATES...
|
|
(define-metafunction π-calculus
|
|
free-vars : π -> (a ...)
|
|
[(free-vars zero) ()]
|
|
[(free-vars (π_1 π_2)) (a_1 ... a_2 ...)
|
|
(where (a_1 ...) (free-vars π_1))
|
|
(where (a_2 ...) (free-vars π_2))]
|
|
[(free-vars (nu a π)) (set-minus (free-vars π) a)]
|
|
[(free-vars (out a_ch a_o π)) (a_ch a_o a ...)
|
|
(where (a ...) (free-vars π))]
|
|
[(free-vars (in a_ch a_x π)) (a_ch a ...)
|
|
(where (a ...) (set-minus (free-vars π) a_x))])
|
|
|
|
;; remove *all* instances of a_kilt from (a ...)
|
|
(define-metafunction π-calculus
|
|
set-minus : (a ...) a -> (a ...)
|
|
[(set-minus (a_1 ... a_kilt a_2 ...) a_kilt) (set-minus (a_1 ... a_2 ...) a_kilt)]
|
|
[(set-minus (a ...) a_kilt) (a ...)])
|
|
|
|
|
|
;; provide variable-not-in as a metafunction:
|
|
(define-metafunction π-calculus
|
|
fresh-in : π a -> a
|
|
[(fresh-in π a) ,(variable-not-in (term π) (term a))])
|
|
|
|
|
|
;; TEST CASES
|
|
|
|
(test--> red (term zero))
|
|
;; simplest communication:
|
|
(test--> red
|
|
(term ((in a b zero) (out a c zero)))
|
|
(term (zero zero)))
|
|
;; check simplest substitution
|
|
(test--> red
|
|
(term ((out a c zero) (in a b (out b b zero))))
|
|
(term ((out c c zero) zero)))
|
|
;; check simple nesting
|
|
(test--> red
|
|
(term (((out z y zero) (out a c zero)) ((in a b (out b b zero)) (out x w zero))))
|
|
(term (((out c c zero) (out x w zero)) ((out z y zero) zero))))
|
|
;; outer context:
|
|
(test--> red
|
|
(term (zero (nu a (((out z y zero) (out a c zero)) ((in a b (out b b zero)) (out x w zero))))))
|
|
(term (zero (nu a (((out c c zero) (out x w zero)) ((out z y zero) zero))))))
|
|
;; with bang replication:
|
|
(test--> red
|
|
(term (zero (nu a (((out z y zero) (out a c zero)) ((! (in a b (out b b zero))) (out x w zero))))))
|
|
(term (zero (nu a ((((out c c zero) (! (in a b (out b b zero)))) (out x w zero))
|
|
((out z y zero) zero))))))
|
|
;; with lifting (depends on fresh name):
|
|
(test--> red
|
|
(term (zero (nu a (((out z y zero) (nu c (out a c (in c c zero)))) ((! (in a b (out b b zero))) (out x w zero))))))
|
|
(term (zero (nu a (nu sent
|
|
((((out sent sent zero)
|
|
(! (in a b (out b b zero))))
|
|
(out x w zero))
|
|
((out z y zero) (in sent c zero))))))))
|
|
|
|
;; need longer examples...
|
|
|
|
;; this on taken from wikipedia:
|
|
(test--> red
|
|
(term ((nu x ((out x z zero)
|
|
(in x y (out y x (in x y zero)))))
|
|
(in z v (out v v zero))))
|
|
(term ((nu x ((out z x (in x y zero))
|
|
zero))
|
|
(in z v (out v v zero)))))
|
|
(test--> red
|
|
(term ((nu x ((out z x (in x y zero))
|
|
zero))
|
|
(in z v (out v v zero))))
|
|
(term (nu sent ((out sent sent zero)
|
|
((in sent y zero) zero)))))
|
|
|
|
(test--> red
|
|
(term (nu sent ((out sent sent zero)
|
|
((in sent y zero) zero))))
|
|
(term (nu sent ((zero zero)
|
|
zero))))
|
|
|
|
|
|
(test-equal (term (rename-innermost a1 a2
|
|
((out a1 a1 zero)
|
|
(nu a1 ((nu a1 (hole (out a1 a1 zero)))
|
|
(out a1 a1 zero))))))
|
|
(term ((out a1 a1 zero)
|
|
(nu a1 ((hole (out a2 a2 zero))
|
|
(out a1 a1 zero))))))
|
|
|
|
(test-equal (term (replicate-as-needed ((out a b zero)
|
|
(! ((nu e (! hole))
|
|
(out c d zero))))
|
|
(out e f zero)))
|
|
(term ((out a b zero)
|
|
(((nu e (hole
|
|
(! (out e f zero))))
|
|
(out c d zero))
|
|
(! ((nu e (! (out e f zero)))
|
|
(out c d zero)))))))
|
|
|
|
(begin (test-equal (term (bound-by hole))
|
|
'())
|
|
(test-equal (term (bound-by (nu x hole)))
|
|
'(x))
|
|
(test-equal (term (bound-by (nu x (nu y hole))))
|
|
'(x y))
|
|
(test-equal (term (bound-by (zero (nu x hole))))
|
|
'(x))
|
|
(test-equal (term (bound-by (! (nu x hole))))
|
|
'(x)))
|
|
|
|
|
|
(test-equal (term (set-minus (w x y x z) x))
|
|
`(w y z))
|
|
|
|
(test-equal (term (free-vars zero)) `())
|
|
(test-equal (term (free-vars (out a b zero))) `(a b))
|
|
(test-equal (term (free-vars (out a b (out c d zero)))) `(a b c d))
|
|
(test-equal (term (free-vars ((out a b zero) (out c d zero)))) `(a b c d))
|
|
(test-equal (term (free-vars (nu a (out a a (out b a zero))))) '(b))
|
|
(test-equal (term (free-vars (in a b (out b b zero)))) `(a))
|
|
|
|
(test-equal (term (subst a1 a2 (nu a3 (out a1 a1 zero))))
|
|
(term (nu a3 (out a2 a2 zero))))
|
|
|
|
(test-equal (term (subst a1 a2 (in a1 a1 (out a1 a1 zero))))
|
|
(term (in a2 a1 (out a1 a1 zero))))
|
|
(test-equal (term (subst a1 a2 (in a1 a2 (out a1 a1 (out a2 a2 zero)))))
|
|
(term (in a2 a3 (out a2 a2 (out a3 a3 zero)))))
|
|
(test-equal (term (subst a1 a2 (in a1 a3 (out a3 a1 zero))))
|
|
(term (in a2 a3 (out a3 a2 zero))))
|
|
|
|
(test-equal (term (subst a1 a2 (! (out a1 a1 zero))))
|
|
(term (! (out a2 a2 zero))))
|
|
|
|
|
|
;; MILNER'S (1990) TRANSLATION FROM CBN-LAMBDA CALCULUS TO PI-CALCULUS:
|
|
|
|
(define-language lambda-calculus
|
|
(e (lam x e) x (e e))
|
|
(x (variable-except lam))
|
|
(E hole (E e))
|
|
|
|
(π (π π)
|
|
zero
|
|
(nu a π)
|
|
(out a a π)
|
|
(in a a π)
|
|
(! π))
|
|
(a variable))
|
|
|
|
|
|
;; not bothering to define reduction...
|
|
|
|
;; Encode-as-π : turn an lc term into a π-calculus term.
|
|
(define-metafunction lambda-calculus
|
|
encode-as-π : e a -> π
|
|
[(encode-as-π (lam x e) a) (in a x (in a v (encode-as-π e v)))]
|
|
[(encode-as-π x a) (out x a zero)]
|
|
[(encode-as-π (e_1 e_2) a) (nu v ((encode-as-π e_1 v)
|
|
(nu a_x (out v a_x (out v a (binding-encode a_x e_2))))))
|
|
(where a_x ,(variable-not-in (term e_2) (term x)))])
|
|
|
|
;; binding-encode : represent a binding. This is the key idea: represent a binding
|
|
;; as a replicating agent that listens on a channel and delivers a channel corresponding
|
|
;; to the argument to anyone that wants it.
|
|
(define-metafunction lambda-calculus
|
|
binding-encode : a e -> π
|
|
[(binding-encode a e) (! (in a w (encode-as-π e w)))])
|
|
|
|
;; test sequence adapted from paper for the term ((lam x x) (lam y y))
|
|
|
|
(test--> red
|
|
(term (encode-as-π ((lam x x) (lam y y)) chan))
|
|
(term (nu v (nu sent ((in v v (out sent v zero)) (out v chan (! (in sent w (in w y (in w v (out y v zero)))))))))))
|
|
|
|
(test--> red
|
|
(term (nu v (nu sent ((in v v (out sent v zero)) (out v chan (! (in sent w (in w y (in w v (out y v zero))))))))))
|
|
(term (nu v (nu sent ((out sent chan zero) (! (in sent w (in w y (in w v (out y v zero))))))))))
|
|
|
|
(test--> red
|
|
(term (nu v (nu sent ((out sent chan zero) (! (in sent w (in w y (in w v (out y v zero)))))))))
|
|
(term (nu v (nu sent (((in chan y (in chan v (out y v zero))) (! (in sent w (in w y (in w v (out y v zero)))))) zero)))))
|
|
|
|
;; observe that the bang clause can be collected: it's listening on a channel that can't escape. So (observes Milner) this
|
|
;; is equivalent to (encode-as-π (lam y y) chan).
|
|
|
|
|