Professional Documents
Culture Documents
Graham Hutton
Languages and Programming Group
Department of Computer Science
University of Nottingham, UK
http://www.cs.nott.ac.uk/~gmh
1
As the paper proceeds we adopt an increasingly categorical For example, eval (Add (Val 1) (Add (Val 2) (Val 3))) =
approach to semantics, to give a deeper understanding of 1+(2+3) = 6, or drawing expressions as trees:
the issues. However, previous knowledge of category theory
is not required. The aim of the paper is to give functional
0 1
programmers new insights into recursion operators, program BB Add CC +0
semantics, and the relationships between them. BB
3
3
3 CC
0
0
BBVal 1 Add
3
3
CC
0
0
2 Denotational semantics BB CC = = 6
eval 3
1 +0
BB CC
3 0
3 0
In denotational semantics [19], the meaning of terms is de- 3 0
@ A
3 0
ned using a valuation function that maps terms into values
Val 2 Val 3 2 3
in an appropriate semantic domain. In this section we ex-
plain how a denotational semantics can be characterised as
a semantics dened by folding over syntactic terms. Looking at this example, we see that an expression is evalu-
Formally, a denotational semantics for a language T of ated by removing each constructor Val (or equivalently, re-
syntactic terms comprises two components: a set V of se- placing each constructor Val by the identity function id on
mantic values, and a valuation function [ ] : T ! V that integers), and replacing each constructor Add by the addi-
maps terms to their meaning as values. The valuation func- tion function (+) on integers. That is, even though eval
tion must be compositional in the sense that the meaning of was dened recursively, its behaviour can be understood
a compound term is dened purely in terms of the meaning non-recursively as simply replacing the two constructors for
of its T -subterms. When the set of semantic values is clear, expressions by the functions id and (+).
a denotational semantics is often identied with a composi-
tional valuation function. 2.2 Fold for expressions
2.1 Arithmetic expressions Abstracting from the specic case of eval, we can consider
the general case of a denotational semantics deno that gives
As an example, let us consider a language of simple arith- meaning to arithmetic expression by replacing each Val by
metic expressions, built up from the set Z of integer values a function f, and each Add by a function g. By denition, a
using the addition operator +. The language E of such ex- semantics dened in this manner will be compositional, be-
pressions is dened by the following grammar: cause the meaning of addition is dened purely by applying
g to the meanings of the two argument expressions:
E ::= Z j E + E
deno (Val n) = f n
We assume that parentheses can be used to disambiguate deno (Add x y) = g (deno x) (deno y)
expressions if required. The grammar for expressions can
be directly translated into a Haskell datatype denition, pa- Since the behaviour of such functions can be understood
rameterised over the type of values for
exibility: non-recursively, why don't we actually dene them in this
data Expr a = Val a | Add (Expr a) (Expr a) manner? This is precisely what fold allows us to do. Using
fold for arithmetic expressions, we can dene denotational
For example, the expression 1+(2+3) is represented by the semantics for expressions simply by supplying the function f
value Add (Val 1) (Add (Val 2) (Val 3)). From now on, that replaces each Val and the function g that replaces each
we mainly consider expressions represented in Haskell. Add. For example, using fold the denotational semantics
Arithmetic expressions have an the obvious denotational eval can be simply dened as follows:
semantics, given by taking V as the Haskell type Int of in-
tegers and [ ] : Expr Int ! Int as the evaluation function eval = fold id (+)
for expressions dened recursively as follows: As another example, using fold we can dene an alternative
[ Val n] = n semantics comp that doesn't evaluate expressions directly,
but rather compiles expressions into a list of instructions for
[ Add x y] = [ x] + [ y] execution using a stack. As for eval, dening the semantics
This denition satises the compositionality requirement, using fold makes it compositional by denition:
because the meaning of compound expressions of the form data Inst = PUSH Int | ADD
Add x y is dened purely by applying + to the meanings of
the subexpressions x and y. The evaluation function can be comp :: Expr Int -> [Inst]
translated directly into a Haskell function denition: comp = fold f g
where
eval :: Expr Int -> Int
f n = [PUSH n]
eval (Val n) = n
g xs ys = xs ++ ys ++ [ADD]
eval (Add x y) = eval x + eval y
For example, comp (Add (Val 1) (Add (Val 2) (Val 3)))
= [PUSH 1, PUSH 2, PUSH 3, ADD, ADD].
The fold function itself can be dened simply by ab-
stracting on the free variables f and g in the general deni-
tion of a denotational semantics deno for expressions:
fold f g (Val n) = f n
fold f g (Add x y) = g (fold f g x) (fold f g y)
2
The type of fold is given by the following inference rule: 3.1 Arithmetic expressions
f :: a -> b g :: b -> b -> b Returning to our example from the previous section, simple
arithmetic expressions have an obvious operational seman-
fold f g :: Expr a -> b tics, given by taking S as the Haskell type Expr of expres-
sions, and ! Expr Expr as the transition relation dened
2.3 Generalising by the following three inference rules:
Of course, the use of fold to dene denotational seman-
tics is not specic to our language of arithmetic expressions, Add (Val n) (Val m) ! Val (n + m)
but can be generalised to many other languages. For exam-
ple, consider extending expressions with integer variables of x ! x 0
y ! y 0
the form Var c for any character c. Then the fold opera- Add x y ! Add x y Add x y ! Add xy
tor would simply be generalised to take an extra argument
0 0
function h to replace each constructor Var in an expression: The rst rule states that two values can be added together to
give a single value, and the last two rules permit the rst rule
fold f g h (Val n) = f n to be applied to either argument of an addition expression.
fold f g h (Add x y) = g (fold f g h x) For example, the (concrete) expression (1 + 2) + (3 + 4) has
(fold f g h y) two possible transitions, because the rst transition rule can
fold f g h (Var c) = h c be applied to either argument of the top-level addition:
In turn, the denotational semantics eval would be gener- (1 + 2) + (3 + 4) ! 3 + (3 + 4)
alised to give the meaning of expressions as functions from
stores (containing the value of each variable) to integers. (1 + 2) + (3 + 4) ! (1 + 2) + 7
Assuming a type Store for stores and a function find for
looking up the value of a variable in a store, eval is dened By repeated application of a transition relation, is is pos-
using the generalised fold as follows: sible to generate a transition tree that captures all possible
execution paths for a syntactic term. For example, the ex-
eval :: Expr Int -> (Store -> Int) pression (1+2)+(3+4) gives rise to the following transition
eval = fold f g h tree, which captures the two possible execution paths:
where
f n = \s -> n (1 + 2) + (3E + 4)
g fx fy = \s -> fx s + fy s yy
EE
EE
h c = \s -> find c s yyy EE
yy
Again, dening the semantics using fold makes it composi- 3 + (3 + 4) (1 + 2) + 7
| "
3+7
Denotational semantics The inference rules dening the transition relation for
m expressions can be easily translated into a Haskell function
denition. The relation is represented as a list-valued func-
Folding over syntax trees tion that maps expressions to lists of expressions that can
be reached by performing a single execution step:
3 Operational semantics trans :: Expr Int -> [Expr Int]
trans (Val n) = []
In operational semantics [17], the meaning of terms is de- trans (Add (Val n) (Val m)) = [Val (n+m)]
ned using a transition relation that captures execution steps trans (Add x y)
in an appropriate abstract machine. In this section we ex- = [Add x' y | x' <- trans x] ++
plain how an operational semantics can be characterised as [Add x y' | y' <- trans y]
a semantics dened by unfolding to transition trees. In turn, we can dene a Haskell datatype for transition trees,
Formally, an operational semantics for a language T of and an execution function that converts expressions into
syntactic terms comprises two components: a set S of states, trees by repeated application of the transition function:
and a transition relation ! S S that relates states to
all the states that can be reached by performing a single ex- data Tree a = Node a [Tree a]
ecution step. (For some applications, a more general notion
of transition relation may be appropriate, but this simple exec :: Expr Int -> Tree (Expr Int)
notion suces here.) If hs; s i 2 !, we say that there is
0
exec e = Node e [exec e' | e' <- trans e]
a transition from state s to state s , and usually write this
0
3
list of residual expressions to be processed to give the sub- 4 Reasoning about semantics
trees by applying the trans function. That is, even though
exec was dened recursively, its behaviour can be under- One of the main reasons for dening the formal semantics
stood non-recursively as simply applying the identity func- of programming languages is to support formal reasoning
tion id to generate the root expression, and the transition about languages and programs written in them. In this sec-
function trans to generate a list of residual expressions to tion we explain how properties of semantics can be proved
be processed to generate the subtrees. using the universality of the recursion operator fold [13, 14],
rather than explicit structural induction.
3.2 Unfold for trees
4.1 Arithmetic expressions
Abstracting from the specic case of exec, we can consider
the general case of an operational semantics oper that gives Consider the following three equations concerning our se-
meaning as trees by using a function f to generate the root mantics for (nite) simple arithmetic expressions:
of the tree, and a function g to generate a list of residual (1) and [deno e' = deno e | e' <- trans e] = True
values to be processed to generate the subtrees:
oper :: a -> Tree b
(2) and [size e' < size e | e' <- trans e] = True
oper x = Node (f x) [oper x' | x' <- g x]
(3) and [n == deno e | n <- vals (oper e)] = True
Since the behaviour of such functions can be understood The rst equation states that the transition function pre-
non-recursively, why don't we actually dene them in this serves the denotational semantics of expressions. The sec-
manner? This is precisely what unfold allows us to do. ond equation states that the transition function decreases
Using unfold for trees, we can dene operational semantics the size of expressions, where the size is dened as the num-
as trees simply by supplying the function f that generates ber of Add constructors. The last equation states that the
the root of the tree, and the function g that generates the denotational and operational semantics are equivalent, in
residual values. For example, using unfold the operational the sense that the integer values in the transition tree gener-
semantics exec can be simply dened as follows: ated by the operational semantics are all equal to the value
exec = unfold id trans obtained from the denotational semantics. The auxiliary
functions size and vals are easy to dene.
The unfold function itself is dened simply by abstract- Equations (1) and (2) above can be proved by induction
ing on the free variables f and g in the general denition of on the structure of e. In turn, by making use of these two
an operational semantics oper as trees: auxiliary results, (3) can be proved by induction on the size
of e. However, the two proofs using structural induction can
unfold f g x = also be proved using the universality of fold, which avoids
Node (f x) [unfold f g x' | x' <- g x] the need for explicit use of induction.
The type of unfold is given by the following inference rule:
4.2 Universality for expressions
f :: a -> b g :: a -> [a]
For simple arithmetic expressions, the universality of fold
unfold f g :: a -> Tree b is captured by the following equivalence:
3.3 Generalising h (Val n) = fn
h (Add x y) = g (h x) (h y)
Of course, the use of unfold to dene operational semantics
is not specic to our language of arithmetic expressions, but m
can be generalised to many other languages. That is, we
have the following simple connection between operational h = fold f g
semantics and unfold operators: This equivalence states that fold f g is the unique solution
to the rst two equations, and can itself be proved using
Operational semantics a simple structural induction. Indeed, the two equations
are precisely the assumptions required to show that h =
m fold f g using structural induction. For specic cases then,
Unfolding to transition trees by verifying the two assumptions (which can typically be
done without the need for induction), we can then appeal
to universality to complete the inductive proof that h =
This is precisely dual to the connection for denotational se- fold f g. In this manner, universality captures a common
mantics given in the previous section. Hence, taking a struc- pattern of inductive proof, just as fold itself captures a
tured approach to program semantics using recursion opera- common pattern of recursive denition.
tors has revealed a duality between denotational and opera- To prove equation (1) above using the universality of
tional semantics that might otherwise have been missed. In fold, it must rst be expressed in the form h = fold f g.
the next section we will see that using recursion operators In this case, h can be dened simply by abstracting over e
also brings benets when proving properties of semantics. on the left-hand side of the equation:
h e = and [deno e = deno e' | e' <- trans e]
4
Abstracting on the right-hand side of the equation gives the EE
constant function \e -> True, which can be expressed in the a yyy
y
EE
EbE
form fold f g by dening f n = True and g x y = x && y. yy E
Hence, by appealing to the universality of fold for expres- yy
2
|
E
"
2
sions, we can conclude that equation (1) is equivalent to the 2 2
following two equations:
a 2b
2
2
a 2 b
2
2
2 2
h (Val n) = True
h (Add x y) = (h x) && (h y)
- - - *
- - - *
a --b a --b a --b a ** b
These equations can now be veried by routine calculations,
-
-
-
-
-
-
*
*
without the need for an explicit induction. Universality can
5
Given the above denitions, it can be shown that the
Proc and Tree types dened as least xpoints are isomorphic
PP ! Pj 0
a
5.2 Functors P j Q !a P j Q
0
P j Q !a P j Q 0
P j Q ! P j Q 0 0
6
6.1 Co-algebras trans' that makes the following diagram commute:
The concept of a co-algebra that we use comes from category
_ _ _unfold
_ _ _trans'
theory, and generalises the idea of a transition function. In Proc _ _ _ _ _ /
Tree
Haskell, a co-algebra for a functor f is a function of type
a -> f a trans' out
T Tree
algebra for the functor T by the following simple denition:
trans' :: Proc -> T Proc 7 Denotational semantics of CCS
trans' p = Node (trans p)
In the previous section we dened an operational seman-
A more general example of a co-algebra concerns the xpoint tics for processes as trees by unfolding a transition func-
type Fix f. In particular, the inverse function out of the tion expressed as a co-algebra. In this section we consider
tag In is a co-algebra for any functor f: the less well-known denotational semantics for processes as
out :: Fix f -> f (Fix f) trees, and show how it can be dened in a dual manner by
out (In x) = x folding a combining function expressed as an algebra .
f a /
f (Fix f)
map (unfold g) Con n -> denode (eval (defn n))
Pre a t -> [(a,t)]
Using this diagram and the fact that out is the inverse to Cho ts -> concat (map denode ts)
In, the unfold function itself can be dened as follows: Par t u -> [(a, comb (Par t' u)) |
(a,t') <- denode t] ++
unfold g = In . map (unfold g) . g [(b, comb (Par t u')) |
(b,u') <- denode u] ++
That is, the function unfold g rst applies the co-algebra [(Tau, comb (Par t' u')) |
g to break down an argument of type a into a structured (a,t') <- denode t,
value of type f a, then applies the function map (unfold (b,u') <- denode u,
g) to recursively process each of the a components to give synch a b]
a value of type f (Fix f), and nally applies the tag In to Res t a -> [(b, comb (Res t' a)) |
give a value of the recursive type Fix f. In this manner, (b,t') <- denode t,
unfold is a general purpose function for producing values of strip a /= strip b]
a recursive type using a simple pattern of recursion. Rel t f -> [(f a, comb (Rel t' f)) |
While previously we dened unfold functions that were (a,t') <- denode t]))
specic to particular recursive datatypes (for example, trees)
the above version of unfold is polytypic [5], in the sense The auxiliary function eval will be dened shortly, while
that it can be used with any recursive datatype that can be denode is the destructor function for trees:
expressed as the least xpoint of a functor.
In the case of processes, because the datatype Tree is denode :: Tree -> [(Act,Tree)]
expressed as the least xpoint of the functor T, and the tran- denode (In (Node xs)) = xs
sition function is expressed as a co-algebra trans' for T, the We refer to comb as a combining function, because it
execution function that maps processes to trees can now be takes a value built by applying a CCS operator to trees
dened using the polytypic version of unfold: rather than to processes, and combines the trees into a single
exec :: Proc -> Tree
tree by interpreting the operator in the appropriate manner
exec = unfold trans'
for trees. For example, the third case for parallel compo-
sition Par t u states that if the tree t has an a-labelled
In summary, we have now expressed the operational se- branch to a subtree t', the tree u has a b-labelled branch to
mantics of processes as trees as the unique function unfold a subtree u', and the actions a and b can synchronise, then
the resulting combined tree has a Tau-labelled branch to the
recursively computed subtree comb (Par t' u').
7
7.2 Polytypic fold 8 Reasoning about CCS
The algebra In is special among all algebras for a functor We have now dened both operational and denotational se-
f, being in fact the initial algebra. Technically, this means mantics for CCS. It is natural to ask how the two semantics
that for any other algebra g :: f a -> a, there is a unique are related. In this section we show that they are equal by
strict function fold g :: Fix f -> a such that the follow- exploiting the universality of the recursion operator fold.
ing diagram commutes [13, 14]:
map (fold g)
8.1 Universality of fold
f (Fix f) /
Fix f
Because fold is a polytypic operator, so this universal prop-
/
fold g
erty is a polytypic proof principle [5]. Returning to our se-
Using this diagram and that fact that In is the inverse to mantics for processes, it is easy to verify that unfold trans'
out, the fold function itself can be dened as follows: is strict, using the denitions of the functions concerned,
together with the strictness of tags dened using newtype.
fold g = g . map (fold g) . out Hence, applying the universal property gives:
That is, the function fold g rst applies the function out unfold trans' = fold comb
to break down an argument of the recursive type Fix f into
a structured value of type f (Fix f), then applies the func- m
tion map (fold g) to recursively process each of the Fix f
components to give a value of type f a, and nally applies comb . map (unfold trans') = unfold trans' . In
the algebra g combine all the a components into a single
result value of type a. In this manner, fold is a general The nal equation above can now be veried by a routine
purpose function for processing values of a recursive type induction on the size of an argument p :: P Proc, where
using a simple pattern of recursion. the size is dened as the number of In tags in p. Hence our
While previously we dened fold functions that were operational and denotational semantics for CCS are equal
specic to particular recursive datatypes (for example, ex- for all processes of nite size. Further work is still required
pressions), the above version of fold is polytypic. Moreover, to extend the proof to processes of innite size.
the denition for fold is precisely dual to that for unfold. The two semantics can also be proved equal using the
Hence, taking an abstract approach to recursion operators dual universal property of unfold rather than that of fold,
has revealed an explicit duality between fold and unfold but the proof works out simpler using the later.
that might otherwise have been missed.
Returning to processes, because the datatype Proc is ex- 9 Summary and future work
pressed as the least xpoint of the functor P, and the com-
bining function is expressed as an algebra comb for P, the In this paper, we have shown how fold and unfold can be
denotational semantics of processes as trees can now be de- used to structure and reason about program semantics within
ned using the polytypic version of fold: Haskell. The paper is based upon categorical work on se-
mantics, but explaining the ideas using Haskell makes them
eval :: Proc -> Tree simpler, accessible to a wider audience, and executable. In-
eval = fold comb teresting topics for future work include:
In summary, we have now expressed the denotational se- The application of the techniques to further examples,
mantics of processes as trees as the unique strict function including languages in dierent paradigms;
fold comb that makes the upper square in the diagram be-
low commute, and dually, the operational semantics of pro- The use of monadic fold operators to structure the de-
cesses as trees as the unique function unfold trans' that notational semantics of programming languages with
makes the lower square commute. imperative eects such as mutable state;
map (fold comb) Exploring recursion operators and algebraic properties
P Proc P Tree that correspond to non-structural patterns of induc-
tion, such as induction on the size of values;
/
_ _ _ _fold
_ _comb
Proc
_ _ _ _ _
_ _ _ _ _ _ _ _ _ _ _
/
/
Tree
Acknowledgements
unfold trans' This work is supported by Engineering and Physical Sciences
trans' out Research Council (EPSRC) research grant GR/L74491 Struc-
tured Recursive Programming. Thanks to colleagues in Birm-
ingham, Cambridge, Glasgow, Nottingham, Oxford, and York
for many useful comments and suggestions.
T Proc /
T Tree
map (unfold trans')
8
References [17] Gordon Plotkin. A structured approach to operational
semantics. Report DAIMI-FN-19, Computer Science
[1] Richard Bird. Constructive functional programming. In Department, Aarhus University, Denmark, 1981.
Proc. Marktoberdorf International Summer School on
Constructive Methods in Computer Science. Springer- [18] Jan Rutten and Daniele Turi. Initial algebra and
Verlag, 1989. nal coalgebra semantics for concurrency. In J.W.
de Bakker et al., editor, Proc. A Decade of Concur-
[2] Richard Bird and Oege de Moor. Algebra of Program- rency | Re
ections and Perspectives, LNCS. Springer-
ming. Prentice Hall, 1997. Verlag, 1994.
[3] Andy Gill, John Launchbury, and Simon Peyton Jones. [19] David A. Schmidt. Denotational Semantics: A Method-
A short-cut to deforestation. In Proc. ACM Confer- ology for Language Development. Allyn and Bacon,
ence on Functional Programming and Computer Archi- Inc., 1986.
tecture, 1993.
[20] Tim Sheard and Leonidas Fegaras. A fold for all sea-
[4] Graham Hutton. Fold . In preparation, 1998. sons. In Proc. ACM Conference on Functional Pro-
[5] Johan Jeuring and Patrik Jansson. Polytypic pro- gramming and Computer Architecture. Springer, 1993.
gramming. In John Launchbury, Erik Meijer, and [21] Daniele Turi and Gordon Plotkin. Towards a math-
Tim Sheard, editors, Advanced Functional Program- ematical operational semantics. In Proc. IEEE Con-
ming, LNCS 1129, pages 68{114. Springer-Verlag, 1996. ference on Logic in Computer Science, pages 280{291.
[6] Geraint Jones. Designing circuits by calculation. Tech- Computer Society Press, 1997.
nical Report PRG-TR-10-90, Oxford University, April
1990.
[7] Geraint Jones and Mary Sheeran. Circuit design in
Ruby. In Staunstrup, editor, Formal Methods for VLSI
Design, Amsterdam, 1990. Elsevier Science Publica-
tions.
[8] John Launchbury and Tim Sheard. Warm fusion:
Deriving build-catas from recursive denitions. In
Proc. ACM Conference on Functional Programming
and Computer Architecture, 1995.
[9] Saunders MacLane. Categories for the Working Mathe-
matician. Number 5 in Graduate Texts in Mathematics.
Springer-Verlag, 1971.
[10] Grant Malcolm. Algebraic data types and program
transformation. Science of Computer Programming,
14(2-3):255{280, September 1990.
[11] Lambert Meertens. Algorithmics: Towards program-
ming as a mathematical activity. In Proc. CWI Sympo-
sium, Centre for Mathematics and Computer Science,
Amsterdam, November 1983.
[12] Erik Meijer. Calculating Compilers. PhD thesis, Ni-
jmegen University, February 1992.
[13] Erik Meijer, Maarten Fokkinga, and Ross Paterson.
Functional programming with bananas, lenses, en-
velopes and barbed wire. In John Hughes, editor, Proc.
Conference on Functional Programming and Computer
Architecture, number 523 in LNCS. Springer-Verlag,
1991.
[14] Erik Meijer and Graham Hutton. Bananas in space:
Extending fold and unfold to exponential types. In
Proc. 7th International Conference on Functional Pro-
gramming and Computer Architecture. ACM Press, San
Diego, California, June 1995.
[15] Robin Milner. Communication and Concurrency. Pren-
tice Hall, 1989.
[16] John Peterson et al. The Haskell language report,
version 1.4. Available on the World-Wide-Web from
http://www.haskell.org, April 1997.