Professional Documents
Culture Documents
Polymonads
Polymonads
Nikhil Swamy?
Gavin Bierman?
Nataliya Guts
Abstract
From their semantic origins to their use in structuring effectful
computations, monads are now also used as a programming pattern to structure code in a number of important scenarios, including for program verification, for information flow tracking, to compute complexity bounds, and for defining precise type-and-effect
analyses. However, whilst these examples are inspired by monads
they are not strictly speaking monadic but rather something more
general. The first contribution of this paper is the definition of a
new categorical structure, the polymonad, which explains these notquite-monadic constructions, and subsumes well-known concepts
including monads, layered monads, and systems of monads and
monad morphisms, among others.
We show how to interpret polymonads in the setting of System F. While this interpretaion enables several useful programming idioms, programming directly with polymonads in F is far
from convenient. Addressing this concern, the second contribution
of this paper is core ML-like programming language, PM, with a
polymorphic type inference algorithm that infers a principal polymonadic type for a program and elaborates it to F in a coherent
wayall unambiguous typing derivations produce elaborated programs with the same semantics. The resulting programming style
in PM is powerful and lightweight, as we demonstrate through a
series of examples.
1.
Introduction
Michael Hicks
Daan Leijen?
Microsoft Research
own prior work [24] has shown that the existing let-structure of a
call-by-value program can be used to infer the placement of the
morphisms in addition to the binds and units.
The monadic programming pattern is sufficiently appealing that
many researchers have developed subtle variations to adapt and apply monads to new problem domains. Examples include Wadler
and Thiemanns [27] indexed monad for typing effectful computations; Atkeys parameterized monad [2], which has been used to
encode disciplines like regions [13] and session types [20]; Devriese and Piessens [6] monad-like encodings for information flow
controls; Danielssons [5] counting monad for computational complexity; and many others. Oftentimes these extensions are used to
prove stronger properties about computations than would be possible with monads, or to prevent undesirable behavior (such as illegal
information flows, memory errors, etc.).
We observe that each of these monad-like patterns involves a
(possibly infinite) family of abstract datatypes M = {M1 , M2 , . . .}
with map operations available at every Mi . However, unlike for traditional monads, a unit operation is provided for only some elements of M, and instead of a bind for each Mi , a bind-like operator with signature a b. Mi a (a Mj b) Mk b is given for
some subset of triples (Mi , Mj , Mk ) in M3 . Additionally, an operation morph:a. Mi a Mj a may be provided for some elements
(Mi , Mj ) in M2 .
This got us wondering: is there some underlying structure
that governs the behavior of these monad-like families of abstract
datatypes? Can one reason about programs using these types with
principles akin to those for monads? Are there effective type inference algorithms that make it simple to program with these types?
In this paper we answer these questions in the affirmative
through the development of a new categorical structure we call
a polymonad (2.1). A polymonad can be characterized simply by
a set of (map:a b. (a b) M a M b) and (join:a. Mi (Mj a)
Mk a) operations on a family of abstract datatypes. The other operations, including bind, unit and morph, can all be expressed as
derived forms. We identify polymonad laws, from which the laws
for monads and monad morphisms can be derived as special cases.
In 2.2, we explain our categorical foundations in the context of
System F [9] and show how the polymonad laws yield principles
that justify the reasoning that programmers routinely conduct on
(and have come to expect of) their programs.
In 3 we give a detailed example of programming with polymonads: information flow security. Whilst demonstrating the feasibility and promise of polymonadic programming, we see that it
can be somewhat cumbersome (this is just the analogue to similar
problems when programming directly with monads [24]). To ease
this task, 4 presents PM, an ML-like source language, and 5
gives a type inference and elaboration algorithm for PM along the
lines of our previous work for monads [24]. We show that our algorithm computes principal types (Theorem 6 and Theorem 8), and
elaborates PM programs to well-typed F programs (Theorem 9).
2013/3/29
2.
RST
R3
RV
A monad on a category consists of a endofunctor and a pair of natural transformations involving that endofunctor satisfying a number of conditions [15]. As mentioned in the introduction, we have
come across a number of monad-like programming patterns that,
in contrast, involve a number of endofunctors and natural transformations between them. This leads to the following rather compact
definition.
Definition 1 (Polymonad). A polymonad over a category C is
a pair of a set M of endofunctors on C and a set J of natural
transformations:
/ UT
2
/W
IR
1 T
Foundations
/ RR
id
/R
The right unit law holds similarly. The associativity law of the
monad is clearly given by the associativity requirement of the
polymonad.
In fact, polymonads support more general versions of the familiar
monad laws. Given a polymonad (M, J), we refer to a map R :
II R J as a unit for R. We also refer to a map : RI
S J as a morphism from R to S. We can show that polymonads
support generalizations of the monad morphism and monad unit
laws.
Theorem 4 (Generalized monad laws). Let (M, J) be a polymonad.
M = {M | M : C C}
J = { : M N P | M, N, P M}
1. For all : RI S, R : II
R, S : II S J the following
diagram commutes:
/R
2. For all 1 : M I
M 0 , 2 : N I N 0 , 3 : LI
L0 , 1 : M N L, 2 : M 0 N 0
L0 J, the following diagram
commutes:
MN
1
1 2
/ M 0N 0
/ L0
2013/3/29
3. For all : II R, : RS
T, : SI T J the following
diagram commutes:
functor set
bind set
bind type
/ RS
/ RS
!
| M, M
| b:,
(M1 , M2 ) M
let bindPQR : a b. P a (a Q b) R b =
a b.p q. joinPQR b (mapP a (Q b) q p)
::=
::=
::=
T
4. For all : II S, : RS
T, : RI T J the following
diagram commutes:
Polymonads in System F
Inlining the binds, and appealing to the laws (in particular, that the
joins are natural transformations), one can show that f is equivalent
to the code below:
let f = a b c. r s t. (joinUTW c joinRSU (T c))
(mapR (S b) (S (T c)) (mapS b (T c) t) (mapR a (S b) s r))
Fortunately, appealing to the laws again (this time using the fact
that maps distribute over function composition), we can prove that
g is equivalent to the function g below, which, like f, uses binds for
sequencing, although in a different order. However, our reasoning
shows that the order does not influence the semantics.
3.
This section presents a detailed example to give a flavor of programming with polymonads, showing how they can be used to enforce information flow security [23] in a language with mutable
references. This polymonad will serve as both motivation for and
illustration of the development of the next several sections. Section 7 develops several additional examples of polymonads.
When programming with polymonads we use the bind-oriented
view, employing the notion of a polymonadic signature defined
in Figure 1. A signature is a pair (M, ), where each element
M of functor set M is a type constructor representing a polymonadic functor, and each element of bind set is a bind operator
bindPQR: a b. P a (a Q b) R b, for some functors P, Q, R in
M. We abbreviate the type of bindPQR as (P, Q) R. When
programming with polymonads we will often use binds having type
(M, Id)M0 ; these play a role similar to morphisms when programming with normal monads, so we write M , M0 as a shorthand.
We also write (P, Q) R to mean b. b:(P, Q) R .
3.1
2013/3/29
Signature
:
:
:
:
intref
intref
intref
intref
L IST H L int
H IST H H int
L int IST L L ()
H int IST H L ()
Sample program
let add interest : intref H intref L IST H H () =
savings. interest.
b HL HH HH (readL interest) (currinterest.
if currinterest > 0 then
b HH HL HH (readH savings) (currbalance.
let newbalance = currbalance + currinterest in
writeH savings newbalance)
else unit HH () (x.x)
3.2
The goal is to ensure that the public outputs are independent of the
secret inputsa property called noninterference [10].
Figure 2 presents a polymonad IST, which implements a heap,
like the standard ST state monad, but prohibits programs that would
leak information. Conceptually, inputs and storage cells are labeled
with security labels l {L, H} which form a lattice with order
L < H, meaning that data labeled H is more secret than data
labeled L. A program is acceptable if data labeled H cannot flow,
directly or indirectly, to computations or storage cells labeled L.
ISTs functor set M consists of four type constructors. Intuitively, a computation with type IST p l potentially writes to references labeled p and returns a -result that has security label l; we
call p the write label and l the output label. For instance, IST H L
is the type of a computation that may write to secret storage cells
while computing a public value. We also introduce types for integer
references: intref H and intref L classify references to an integer
value labeled H and L, respectively. We can then give functions for
reading and writing references, whose types are given in the second
portion of the figure (we elide allocation functions for simplicity).1
Each return type has form IST p l . Function readH has l = H
since the returned -value is sensitive, but in all other cases it is
not. Function writeL has p = L, since a write is occurring to a lowsecurity cell, but in all other cases it is not. (Thus, H is the most
permissive write label, and L is the most permissive output label.)
The allowed binds consist of units unit pl, which when composed with the identity function lifts a normal term into an IST
computation; identity maps map pl and the related functions
app pl (which, as is true of maps and apps in general, can be
implemented as app pl = x.f.map pl (f x) (x.x)). The
remaining binds are those satisfying the given conjunction of inequalities which enforce proper information flow. When composing
a computation IST p1 l1 with a function IST p2 l2 , the
type of b p1 l1 p2 l2 p3 l3 requires l1 v p2 to prevent the second
1 A more elegant formulation would be to have functors IST that are param-
Better programmability
While programming directly with polymonads, as done for the program given in Figure 2, is feasible, it is by no means easy. First of
all, it is tedious to compose the various binds with the actual program logic, and the result is painful to read. Even worse, in ML
this code is pointless: the state monad is built into the language!
We could have implemented IST on top of standard references and
implemented the various binds as identity functions, in which case
their use is not to add functionality, but rather to enforce the information flow property. Finally, sometimes the most straightforward
insertion of binds leads to less reusable code: sometimes it makes
sense to parameterize a function by the binds it uses, rather than
inlining them directly.
We can solve all of these problems by implementing type inference with elaboration [24]. The goal is to allow the programmer to
write the program in a direct style, treating computations of type
M as if they were values of type . Then the compiler will perform type inference to infer a principal type for the program, and at
the same time rewrite it to insert the necessary, and most general,
polymonadic binds. In the next section, we present a type inference
algorithm that can produce a program like the one in Figure 2 by
performing inference on the following (more readable) program:
let add interest = savings. interest.
let currinterest = readL interest in
if currentinterest > 0 then
let currbalance = readH savings in
let newbalance = currbalance + currinterest in
writeH savings newbalance
else ()
Then, in Section 5, we prove that all such rewritings that the compiler could perform are coherent, i.e., they will have the same runtime semantics. Sections 6 and 7 present extensions and improvements to the basic approach.
4.
eterized by their labels (i.e., l {H , L} could be type indexes) and reference functions whose types are likewise parameterized over labels. Section 7 shows how to generalize PM to support type indexes, and presents
the generalization of IST along with several other examples.
Figure 3 presents the syntax of PM, an ML-like, call-by-value language with a type system that enables a lightweight form of polymonadic programming. PM programmers author programs in direct style, using the standard notation for -abstractions, function
2013/3/29
values
expressions
v
e
functors
value types
type schemes
bind type
constraint bag
::=
::=
|
::=
::=
::=
::=
::=
x | c | x.e
v | e1 e2 | let x = e1 in e2
if e then e1 else e2 | letrec f = v in e
|M
a | T | 1 m 2
a.P
(m1 , m2 ) m
| , P
argument using two bind operators, and then apply the function.
(TS-If) is also similar, since we sequence the expression e in the
guard with the branches. As usual, we require the branches to
have the same type. This is achieved by generating morphism
constraints, m2 , m and m3 , m to coerce the type of
each branch to a functor m before sequencing it with the guard
expression.
4.2
Principal types
The type rules admit principal types, and there exists an efficient
type inference algorithm that finds such types. The way we show
this is by a translation of polymonadic terms (and types) to terms
(and types) in OML [12] and prove this translation is sound and
complete: a polymonadic term is well-typed if and only if its translated OML term has an equivalent type. OMLs type inference algorithm is known to enjoy principal types, so a corollary of our
translation is that principal types exist for our system too.
We encode terms in our language into OML as shown in Figure 5. We rely on four primitive OML terms that force the typing of
the terms to generate the same constraints as our type system does:
ret for lifting a pure term, do for typing a do-binding, app for typing an application, and if for conditionals. Using these primitives,
we encode values and expressions of our system into OML.
We write P | `OML e : for a derivation in the syntax directed
inference system of OML (cf. Jones [12], Fig. 4).
Theorem 6 (Encoding to OML is sound and complete).
Soundness: Whenever P | ` v : we can also derive P | `OML
JvK? : in OML. Similarly, when P | ` e : m we have
P | `OML JeK : m .
Completeness: If we can derive P | `OML JvK? : , there also exists a derivation P | ` v : , and similarly, whenever P | `OML
JeK : m , we also have P | ` e : m .
The proof is by straightforward induction on the typing derivation of the term. It is important to note that our system uses the
same instantiation and generalization relations as OML which is
required for the induction argument. Moreover, the constraint entailment over bind constraints also satisfies the monotonicity, transitivity and closure under substitution properties required by OML.
As a corollary of the above properties, our system admits principal
types via the general-purpose OML type inference algorithm.
4.3
Ambiguity
One might then think that we could simply infer types for PM by
translating programs to Haskell, which uses OMLs inference algorithm. However, this strategy is unsatisfactory, as Haskell would
reject many useful programs. Translated to our setting, Haskell rejects as ambiguous any term whose type
a.P includes a
variable such that is free in P but not we call such variables open. Haskell, in its generality, must reject such terms since
the instantiation of an open variable can have operational effect,
while at the same time, since the variable does not appear in , the
instantiation for it can never be uniquely determined by the context
in which the term is used.
Rejecting all types that contain open variables would be unacceptable for PM. Many extremely simple terms have principal types that contain open variables; e.g., f x.f x has the type
a b m m m. (Id,m)m, (Id,m)m (a m b) a m b,
which contains the open variable m. However, with our knowledge of the semantics of polymonadic binds, we can prove that
such programs are indeed unambiguousthis is the development
of the next section.
2013/3/29
= [
/
a][m/
] P |= P1
P |= (
a. P1 ) >
(TS-Inst)
P | ` v :
v {x, c}
P |= (v) >
P | ` v :
(TS-XC)
P | ` e : m
P | ` v :
P, Id , m | ` v : m
P |= >
P1 | ` v :
P | , x:Gen(, P1 ) ` e : m 0
P | ` let x = v in e : m 0
P | ` e1 : m1 1
P | ` e1 : m1 bool
P | , f :Gen(, P1 ) ` e : m 0
(TS-Rec)
P | ` e2 : m2 2
P |= (m1 , m4 ) m5
P | ` e1 e2 : m5
P | ` e2 : m2
(TS-Lam)
P | ` letrec f = v in e : m 0
P | , x:1 ` e2 : m2 2
e1 6= v
P | ` let x = e1 in e2 : m3 2
P | ` e1 : m1 (2 m3 )
P | , x:1 ` e : m 2
P | ` x.e : 1 m 2
P1 | , f : ` v :
(TS-V)
P 0 . P
P |= P 0
P |= P 0
P | ` e3 : m3
(TS-Do)
P |= (m2 , m3 ) m4
(TS-App)
P |= m2 , m, m3 , m, (m1 , m) m0
(TS-If)
P | ` if e1 then e2 else e3 : m0
ret
do
app
if
: . (Id , )
: 1 2 . ((1 , 2 ) )
1 ( 2 )
: 1 2 3 4 . ((1 , 4 ) , (2 , 3 ) 4 )
1 ( 3 ) 2
: 1 2 3 0 . (2 , , 3 , , (1 , ) 0 )
1 bool (() 2 ) (() 3 ) 0
JxK?
JcK?
Jx.eK?
=x
=c
= x.JeK
JvK
Je1 e2 K
Jlet x = v in eK
Jlet x = e1 in e2 K
Jif e1 then e2 else e3 K
Jletrec f = v in eK
= ret JvK?
= app Je1 K Je2 K
= let x = JvK? in JeK
= do Je1 K Jx.e2 K?
(with e1 6= v)
= if Je1 K ().Je2 K ().Je3 K
= letrec f = JvK? in JeK
5.
In this section, we present a type inference and elaboration algorithm for PM. We prove our algorithm sound and complete with
respect to the syntax-directed system of Figure 4 (by proving it
sound and complete with respect to OMLs algorithmic judgment).
We also prove that our algorithm elaborates well-typed PM programs to well-typed F programs, and, most importantly, that all
unambiguous elaborations are coherent. Thus, programmers can reliably view our syntax-directed system as a specification without
being concerned with the details of how programs are elaborated.
m3
5.1
m2
m1
?
m2
m3
m4
m1
With our graph-view of constraints in mind, we turn to describing the algorithm in Figure 6. The judgment has the following shape, where the subscripts indicate inputs and outputs:
in | out | Pout | in `rin ein : tout ; eout . Informally, given a substitution in of the free variables in the context in , and given some
2013/3/29
m1
m2
in | out | Pout | in `rin ein : tout ; eout where t ::= | m and ::= a |
| 0 | P | , x:a `r]{a} e : m ; e
| 0
| P | `r x.e : a m ; x:a.e
| 0 | P | `r v : ; v
(TA-Lam)
| 0
6 r ftv(0 , P, )
| P, Id , | `r v : ; bId,Id, v x.x
| 1 | P1 | , f :a `r]{a} v : ; v
r0 = r ] {a} ftv(, 1 )
2 = mgu1 (a, ) 2 1 | 3 | P | , f :Gen(, 2 1 , P1 ) `r0 e : m 0 ; e
| 3 2 1 | P | `r letrec f = v in e : m 0 ; letrec f = v in e
(TA-V)
(TA-Rec)
| 1 | P 1 | `r v : 1 ; v
r0 = r ftv(P1 , 1 , 1 )
1 | 2 | P | , x:Gen(, 1 , P1 1 ) `r0 e : m ; e
| 2 1 | P | `r let x = v in e : m ; (x: . e) abs(P1 , v)
e1 6= v
| 1 | P1 | `r e1 : m1 1 ; e1
r0 = r ftv(1 , P1 , m1 1 )
1 | 2 | P2 | , x:1 `r0 e2 : m2 2 ; e2
3 6 r0 ftv(2 , P2 , m2 2 )
| 2 1 | P1 , P2 , (m1 , m2 ) 3 | `r let x = e1 in e2 : 3 2 ; bm1 ,m2 ,3 e1 x: . e2
(TA-Do)
| 1 | P1 | `r e1 : m1 1 ; e1
r0 = r ftv(1 , P1 , m1 1 ) 1 | 2 | P2 | `r0 e2 : m2 2 ; e2
a, 3 , 4 , 5 6 r0 ftv(2 , P2 , m2 2 )
3 = mgu2 1 (1 , 2 3 a)
| 3 2 1 | P1 , P2 , (m1 , 4 ) 5 , (m2 , 3 ) 4 | `r e1 e2 : 5 a ; bm1 ,4 ,5 e1 x: . bm2 ,3 ,4 e2 x
| 1 | P1 | `r e1 : m1 1 ; e1
1 | 2 | P2 | `r0 e2 : m2 ; e2
2 1 | 3 | P3 | `r00 e3 : m3 0 ; e3
4 = mgu3 2 1 ((1 , ), (bool, 0 ))
0 = 4 3 2 1
(TA-Let)
(TA-App)
r0 = r ftv(1 , P1 , m1 )
r00 = r0 ftv(2 , P2 , m2 )
4 , 5 6 r00 ftv(4 , 3 , P3 , m3 )
P = P1 , P2 , P3 , m2 , 4 , m3 , 4 , (m1 , 4 ) 5
| 0 | P | `r if e1 then e2 else e3 : 5 ; bm1 ,4 ,5 e1 x: . if x then (bm2 ,Id,4 e2 x.x) else (bm3 ,Id,4 e3 x.x)
v {x, c} ftv(, ) r
dom(00 ) = , internal
(v) =
.0 P
internal = ftv(0 P ) \
.freshr 00 ()
0 . 6= 0 00 () 6= 00 ( 0 )
| 00 0 001 | 00 P | `r v : 00 ; app(v, 00 P )
where
Gen(, , P )
app(e, (P, (m1 , m2 )m3 ))
abs(((m1 , m2 )m, P ), e)
=
=
=
(TA-If)
(TA-XC)
2013/3/29
Our next theorem establishes that elaborated programs computed by our algorithm are well-typed in F.
Coherence
While our algorithm (and by Theorem 8 OMLs algorithm) computes principal types, as discussed in Section 4, without further
analysis we would have to reject many programs as ambiguous.
OMLs ambiguity restriction rejects any derivation P | `W e : t
where the constraints P contain free variables that are not also free
in or in t. However, for PM, knowing that the semantics of the
inferred constraints is governed by the polymonad laws, we can
do significantly better. In particular, we only deem derivations ambiguous if they run afoul of the following, less restrictive condition.
Definition 10 (Unambiguous derivations). A derivation | 0 | P | `
e : t ; e is unambiguous if and only if for all v V in the
graph-view G = (V, E, A) of 0 P , if in-degree(v) = 0 or
out-degree(v) = 0, then A(v) = M for some constant M, or
A(v) ftv(0 (, t)).
The main result of this section proves that all possible elaborations of an unambiguous derivation produced by our algorithm
have the same semantics, i.e., type inference is coherent (Theorem 15), so long as the program uses a closed, principal relational
polymonad, a common class of polymonads defined as follows.
Definition 11 (Principal relational polymonad). A polymonad
(M, ) is a principal relational polymonad if and only if for any
set F M2 , and any {M1 , M2 } M such that {(m, m0 )M1 |
(m, m0 ) F } and {(m, m0 ) M2 | (m, m0 ) F } ,
then there exists M M such that {M , M1 , M , M2 } ,
and {(m, m0 ) M | (m,Fm0 ) F } . We call M the principal
join of F and write it as F
.I
RW
/ TI
/V
IRS
I.
01 .S
IT
/ W 0S
02
/V
Analyzing principality. Informally, in a principal relational polymonad, if there is more than one way to sequence computations in a
pair of functors, there must be a best way to combine them. This
best way is the principal join of the functors, and all other ways to
combine the functors are related to the principal join by morphisms.
Intuitively, it should be clear that this principality requirement is not
particularly onerous. Supposing we had (P, Q) R and (P, Q) S
Proof. The two solutions and 0 may only differ on the variables
assigned to nodes with non-zero in- and out-degree in the graphview G = (V, E, A) of P , since (, t) = 0 (, t), and, by assumption, we have an unambiguous derivation. The proof proceeds
by induction on n, the number of these nodes, showing that any
2013/3/29
M7
M8
M5
M6
...
M7
...
...
M5
M3 /M03
. . . A/B . . .
8 O f
M4 /M04
...
...
...J...
...
...
M4 /M04
...
M2 /M02
...
...
?O
6 JO h
...
M6
M1 /M01
M5
M3 /M03
...
m8
M4 /M04
m7
...
M2 /M02
...
. . . A/B . . .
...
0
P2 /P2
M1 /M01
...
...
M3 /M03
......
m6
M2 /M02
...
m4
...
0
P1 /P1
M1 /M01
9 O e
...
m3
m5
m2
...
0
P0 /P0
P
m1
...
M7
...
M6
...
M8
M8
Figure 7. Constraint graphs used to illustrate the proof of coherence (Theorem 15)
syntactic discrepancy between the two solutions is semantically irrelevant. Notice that we carry out our coherence argument independently of the unification constraints out in these are simply treated
as part of the solutions and 0 .
Base case n = 0: Trivial, e is syntactically equal to 0 e.
Induction step: From the induction hypothesis: For all constraintbags P with solutions and 0 differing on at most i variables,
and 0 are coherent.
Topologically sort the graph-view G of P , such that each vertex
m is assigned an index greater than the index of all vertices m0 such
that (m, m0 ) is a directed edge in G. That is, leaf nodes have the
highest indices. Let v be the vertex with the greatest index, such
that A(v) = and () = A and 0 () = B, for A 6= B. Note, v
must have non-zero in and out-degree.
So, P has a sub-graph in the neighborhood of v, call it P , with
shape as shown in left-most graph in Figure 7 Note, since P is
a DAG, m1 may equal m2 etc., although for the purposes of this
argument that is irrelevant.
Now, consider P and 0 P (call them P0 and P00 , shown
together, second from left in Figure 7). Since has the greatest
index of any variable that differs among and 0 , all the immediate
predecessors of (i.e., m5 , . . . , m8 ) have identical assignments in
the two solutions (i.e,. M5 , . . . , M8 ), although the assignments to
m1 . . . m4 may differ, i.e., m1 could be assigned M1 in P0 and M01
in P00 , etc. Now, since we have a principal relational polymonad,
there exists a principal join of {(M5 , M6 ), . . . , (M7 , M8 )}call it
J. So, we can rewrite P0 and P00 to P1 and P10 (second from right
in Figure 7): by the polymonad laws, we have that the composition
of (M5 , M6 ) J and J , A, is the same as the (M5 , M6 ) A
(similarly, for B).
Now, we apply the closure requirement contravariantly in P1 to
b : (M3 , A) M1 and s : J , A and get that b : (M3 , J)
M1 must exist in , and from the associativity of binds, we get
that the composition of b and s must be equal to b. Likewise,
the composition of (A, M4 ) M2 and J , A is equivalent to
(J, M4 ) M2 which must also exist in . Similar transformations
are justified for B and the primed versions of the functors in P10 .
Applying these transformations, we get P2 and P20 , the right-most
graph in the figure.
Now, replacing the sub-graph P0 with the equivalent sub-graph
P2 in P ; and replacing P00 with the equivalent sub-graph P20 in
0 P , we obtain two constraints DAGs that differ in at most i nodes.
So, we apply the induction hypothesis and conclude.
S-
G(P, , P 0 ) = (V, E, A)
A(.2)
in-degreeE (.2) = 2
{i, j} = {0, 1} A(.i) = Id
simplify()
P, , P 0
A(.2) 7 A(.j)
S-
G(P, , P 0 ) = (V, E, A)
A(.i) = Id
{i, j} = {0, 1}
A(.j)
out-degreeE (.j) = 1
simplify()
P, , P 0
A(.j) 7 A(.2)
G(P ) = (V, E, A)
V = {.i | A(.i) = }
F = {(A(.0), A(.1)) | v V .{(.0, v), (.1, v)} E}
S-t
F
simplify()
7 F
simplify()
simplify()
simplify()
simplify()
A simple syntactic transformation on constraints can make inferred types easier to read. For example, the function Hide(P ) below hides duplicate constraints, identity morphisms (which are trivially satisfiable), and constraints that are entailed by the signature.
Hide(P, , P 0 ) = Hide(P, P 0 ) if P, P 0 = m , m |=
Hide(P )
= P
otherwise
This rule employs the judgment P , defined in Figure 8, to simplify constraints by eliminating some open variables
in P (via the substitution ) before type generalization. There are
three main rules in the judgment (S-), (S-) and (S-t), while the
last two simply take the transitive closure.
The simplification rules operate on the graph-view of a constraint bag. Rule (S-) solves monad variable with monad m for
2013/3/29
6.
...
Id
using (S-)
Id
simplify(,)
...
using (S-)
/
simplify(,)
Id
...
...
/!
Id
/ o
simplify(,)
...
M3
using (S-t)
M4
where
M1
...
M2
...
M1
/Jo
...
M3
M2
M4
F
J = {(M1 , M2 ), . . . , (M3 , M4 )}
In this case, if we have a variable such that all its in-edges are
from pairs of constant functors Mi , then we can simply apply the
join function to compute a solution for . For a closed principal
relational polymonad, if such a solution exists, this simplification
does not impact solvability of the rest of the constraint graph.
Parameterized signatures:
k-ary constructors M
ground constructor M
bind set
bind specifications s
theory constraints
::= | M/k, M
::= M
::= | b:s,
::= .
(M1 , M2 ) M3
theory entailment : 2P b
::=
monadic types
m ::=
a | T | 1 m 2
M|
P 0 . P ; b;
P |= P 0
Figure 9. PM syntax (extends/modifies Figure 3)
consider nodes in the graph in topological order and, say, apply (St) first, since, if it succeeds, it eliminates a variable. For principal
relational polymonads and acyclic constraint graphs, this process
would always terminate.
However, if unification constraints induce cycles in the constraint graph, simply computing joins as solutions to internal variables may not work. As mentioned in the Introduction, this should
not come as a surprise. In general, finding solutions to arbitrary
polymonadic constraints is undecidable, since, in the limit, they can
be used to encode the correctness of programs with general recursion. Nevertheless, simple heuristics such as unrolling cycles in the
constraint graph a few times may provide good mileage, and such
heuristics are justified by our coherence proof.
7.
Indexed polymonads
10
2013/3/29
Example. Recall the information flow example we gave in Section 3.2. Its principal type (given below) is hardly readable:
However, after applying (S-) and (S-) several times, and then
hiding redundant constraints, we simplify P0 to (IST H L, 6 )
26 , (Id, Id)6 , (IST H H, Id)6 . Then, applying (S-t) to 6
we get (IST H L, IST H H) 26 , which cannot be simplified
further, since 26 appears in the result type.
Pleasingly, this process yields a simpler type that can be used
in the same contexts as the original principal type, so we are not
compromising the generality of the code by simplifying its type.
Lemma 16 (Simplification improves types). For a closed, principal relational polymonad, given and 0 where is
.P
and 0 is an improvement of , having form
0 .P where
simplify()
We use the type index send to denote a protocol state that requires a message of type to be sent, and then transitions to . Similarly, the type index recv denotes the protocol state in which
once a message of type is received, the protocol transitions to .
We also use the index end to denote the protocol end state. The
signatures of two primitive operations for sending and receiving
messages captures this behavior.
=
::=
=
=
=
Id, A/2
bId : (Id, Id) Id,
mapA : , . (A , Id) A ,
appA : , . (Id, A ) A ,
unitA : . (Id, Id) A ,
bindA : . (A , A ) A
send : .
A (send ) ()
recv : . ()
A (recv )
Using these definitions, consider the following PM program that
implements one side of a simple protocol that sends a message x,
waits for an integer reply y, and returns y+1.
let go = x. let = send x in incr (recv ())
IST/2
l1 v l2 | 1 , 2
bId : Id , Id,
unitIST : p, l.Id , IST p l,
mapIST : p, l, p0 , l0 .p0 v p, l v l0
IST p l , IST p0 l0 ,
appIST : p1 , l1 , p2 , l2 .p2 v p1 , l1 v l2
(Id, IST p1 l1 ) IST p2 l2 ,
bIST : p1 , l1 , p2 , l2 , p3 , l3 .
l1 v p 2 , l 1 v l3 , l 2 v l3 , p 3 v p 1 , p 3 v p 2
(IST p1 l1 , IST p2 l2 ) IST p3 l3
Note that the theory constraints for session types are straightforward, and can be solved by unification when instantiating the final
program (e.g., to call go 0).
::=
::=
| A1 . . . An | | > | 1 2
0 | = 0 | ,
11
2013/3/29
Finally, bindce composes two computations such that the future effect of the first computation includes the effect of the second one,
provided that the prior effect of the second computation includes
the first computation; the effect of the composition includes both
effects, while the prior effect is the same as before the first computation, and the future effect is the same as after the second computation.
cursion have the same semantics. Tate also does not address type
inference.
References
[1] M. Abadi, A. Banerjee, N. Heintze, and J. Riecke. A core calculus of
dependency. In POPL, volume 26, pages 147160, 1999.
[2] R. Atkey. Parameterised notions of computation. J. Funct. Program.,
19(3-4):335376, 2009.
8.
Related work
A variety of past work has aimed to refine the conventional notion of monads. Several examples, including Atkeys parameterized
monads [2], Wadler and Thiemanns indexed monads [27], and applications thereof, were cited in the introduction and given in Section 7. Each of these constructions can be viewed as an instance of
a polymonad. Filliatre [8] proposed generalized monads as a means
to more carefully reason about effects in a monadic style, and his
work bears a close resemblance to Wadler and Thiemanns. Generalized monads can also be seen as instances of polymonadsit is
easy to show that the polymonad laws imply Filliatres six required
identities. Conversely, it is clear that some useful examples cannot
be expressed using any of these prior refinements to monads; for
example, our IST polymonad cannot be expressed due to its exclusion of certain (information-flow-violating) compositions. Thus
polymonads provide greater expressive power.
Kmetts Control.Monad.Parameterized Haskell package [14]
provides a typeclass for ternary bind-like operators that have a signature resembling ours (m1 , m2 ) m3 . One key limitation is that
Kmetts binds must be functionally dependent; i.e., m3 must be
a function of m1 and m2 . As such, it is not possible to program
morphisms between different monadic constructors, i.e., the pair of
binds (m1 , Id) m2 and (m1 , Id) m3 would be forbidden, so
there would be no way to convert from m1 to m2 and from m1 to
m3 in the same program. Kmett also does not permit polymorphic
unitshe requires units into Id, which may later be lifted. But this
only works for first-order code before running afoul of Haskells
ambiguity restriction. Polymonads do not have either limitation.
Kmett does not discuss laws that should govern the proper use of
non-uniform binds.
Another line of past work has focused on making monadic
programming easier. Haskells do notation exposes the structure
of a monadic computation, and typeclass inference can determine
which binds and units should be used, but the placement of morphisms is left to the programmer. The problem is that the use of
morphisms (e.g., if defined as a typeclass) would frequently lead
to open type variables, which Haskells typeclass inference deems
ambiguous. Inference with Kmetts class has the same problems.
For ML, we have already discussed our own prior work [24] in detail throughout the paper.
Concurrently with our work Tate developed a semantic framework called productors for describing the sequential composition
of effects [25]. Polymonads are closely related to a special class
of productors called productoids, although subtle differences in our
formulations mean that neither subsumes the other precisely. Productors allow a more general class of non-compositional effect system than productoids, and some of those systems are not polymonads either. However, this does not seem to provide any additional
expressive power since as Tate conjectures (in private communication), any productor can be encoded as a productoid by adding
more functors and joins. In all examples we have seen, these can be
encoded as polymonads as well. Tate proves a coherence result in a
first-order imperative setting establishing that sequential compositions of productor joins are fully associative. Our coherence result
is different in that it proves that all well-typed elaborations of a
higher-order program with branching, function application, and re-
12
2013/3/29