We will also present a new approach called reusable monadic semantics, which is ... gers (like Haskell Integers), so L1 expressions denote integer values. ...... versity, Japan, pages 98â122, Singapore, New Jersey, London, Hong Kong,.
Reusable Semantic Specifications of Programming Languages J. E. Labra Gayo1 1
Department of Computer Science, University of Oviedo C/ Calvo Sotelo S/N, 3307, Oviedo, Spain {labra}@lsi.uniovi.es
Abstract. In this tutorial, we compare the main approaches for programming language semantic specification from the point of view of modularity and reusability. We will also present reusable monadic semantics, which combines monadic semantics with generic programming concepts and we will present the specification of simple functional, imperative and logic programming languages. The specifications are made from reusable semantic building blocks, which facilitates the comparison between different paradigms.
1. Introduction The semantic specification of most programming languages, from Fortran to Java [JSGB00], is made in natural language. Natural language descriptions cause a number of well known problems like ambiguity and lack of rigorous descriptions. Whereas for syntactic description, the BNF notation is widely used by programming language designers. In the case of semantic specifications, the existing formal techniques are not so popular [Mos96, Wat96]. We consider that the ideal requirements of a semantic specification technique should be: • Non ambiguity. The proposed technique should facilitate the development of rigorous descriptions that clearly identify the behaviour of a given program. • Demonstration. It should have a mathematical basis that allows to demonstrate properties of programs. • Prototype. It should be possible to automatically obtain executable language processors from the language descriptions. • Modularity. The technique must allow to develop the specification in an incremental way. It should be possible to add an orthogonal feature to a language withoud modifying previous independent definitions. For example, when we add variables to a language with arithmetic expressions, the specification made for arithmetic expressions should not have to be modified. • Reusability. The technique should allow to reuse common features specifications between different languages. The ideal solution would be to have a library of semantic building blocks that the language designer could add or remove in a simple way. As an example, arithmetic expressions are used in multiple languages in a similar way, so their specification could be taken from that library. • Readability. Semantic specifications should be readable by persons with different backgrounds. In fact the behaviour description of a programming language affects all the people that intervenes in the software development process. The ideal would be that all that people could understand the language specification.
• Flexibility. The technique must be able to cope with the great variety of existing programming languages and paradigms. In fact, the technique should allow to investigate new kinds of languages and paradigms by promoting feature combination. • Experience. Finally, it should be possible to apply the formal technique to real programming languages. Some techniques are applicable to small languages but are difficult to apply to more complex languages. In this paper, we introduce and compare some popular techniques taking into account those requirements. We put special emphasis on modularity and reusability properties, as we consider that most of the existing techniques lack them. We will also present a new approach called reusable monadic semantics, which is a combination of monadic semantics and generic programming concepts. This approach facilitates modular and reusable specification of programming languages and has already been applied to simple imperative [LCLG01], functional [LLCC01], logic [LCLC01b] and object-oriented programming languages [Lab01]. The tutorial is organized as follows. Section 2. compares several semantic specification techniques. Section 3. presents modular monadic semantics and in section 4. we present the generic programming concepts that will be used in the rest of the paper. Finally, section 5. presents reusable monadic semantics and apply it to specify different programming languages.
2. Comparison of Semantic Specification Techniques The comparison is made by describing three languages in an incremental way. Firstly, we will specify a very simple language of arithmetic expressions. Then, we add variables to the language and finally, we add assignments and imperative commands. The goal is to offer a common framework to observe the differences between the techniques. For lack of space, we will not justify the semantic descriptions and assessments. A more thoroughly comparison is presented in [Lab01]. 2.1. Natural Language • L1 consists only of simple arithmetic expressions. The abstract syntax is given by expr ::= Const Integer | expr + expr
— Numeric constant — Addition
The language has only one primitive type that represents arbitrary precision integers (like Haskell Integers), so L1 expressions denote integer values. • L2 adds variables to L1 with the syntax expr ::= . . . | Var Name
— L1 definitions — identifier
From a semantic point of view, the evaluation of an expression with an identifier x depends on the value that x has in the context of that evaluation. Although context specification is different from one formalism to another, in order to simplify the
different presentations, ς will denote a value of type State that will represent the environment in which we evaluate identifiers. We also suppose that we have the following operations: – upd : State → Name → Value → State, upd ς x v returns the state obtained from ς after assigning the value v to x . – lkp : State → Name → Value, lkp ς x returns the value of v in ς, we consider it a total function for simplicity. • L3 adds commands formed by sequences and assignments to L2 comm ::= Name := expr | comm ; comm | skip
— Assignment — Sequence — Empty command
Remark. The specification of variables is usually done in two levels: an environment that maps variable names to locations, and the store that maps locations to values. To simplify the presentation, we will only use one level, so the environment directly assigns a value to a variable and can be updated. 2.2. Structured Operational Semantics Structured Operational Semantics was presented by G. Plotkin [Plo81] to specify the elementary transitions of a program using inference rules defined by induction over the structure. L1 has only arithmetic expressions. The operational semantics defines an evalueval ation function : Expr → N that relates an expression with the value it denotes. The inference rules are: he1 i hConst ni
eval
eval
he1 + e2 i
n
he2 i
v1
eval
eval
v2
v1 + v2
When we add variables, the evaluation function must be modified to consult the eval value in a context ς. The new type will be : Expr → State → N. Which means that the meaning of an expression is not a value, but a function that depends on a context ς ∈ State. The inference rule will be
hV ar v, ςi
eval
lkp v ς
That modification affects the above definitions. So the new definitions should be he1 , ςi hConst n, ςi
eval
n
eval
v1
he1 + e2 , ςi
he2 , ςi eval
eval
v2
v1 + v2
In L3 we add imperative commands that can modify the context. The meaning of an imperative command takes part of the program and a context and returns the rest of the program to execute with the new context.
step
: (Comm × State) → (Comm × State)
The inference rules will be
he, ςi hx := e, ςi
step
eval
hc1 , ςi
v
hskip, upd ς x vi
hskip; c, ςi
step
hc, ςi
hc1 ; c2 , ςi
step
hc01 , ς 0 i
step
hc01 ; c2 , ς 0 i
The above operational semantics is also called single-step semantics because it specifies the trace of the execution step by step. The final semantics of a command is step obtained as the transitive closure of . Traditional Structural Operational Semantics does not develop modular and reusable specifications. At the same time, the operational character complicates the verification of program properties. It is not difficult to build prototype interpreters from operational descriptions and this technique has already been applied to the specification of several programming languages from different paradigms. Natural semantics [Kah87] is a variation of operational semantics where we define global transitions instead of elementary transitions and is also called big-step semantics. Nevertheless, it has the same modularity and reusability problems as structural operational semantics. Recently, P. Mosses, inspired by modular monadic semantics, proposed a modular structural operational semantics [Mos99]. 2.3. Denotational Semantics In denotational semantics the behaviour of a program is described modelling the meanings as basic mathematical entities. It was firstly proposed by C. Strachey in 1967 [Str00] using λ-calculus. Afterwards D. Scott y C. Strachey collaborate in 1969 to establish the theoretical basis using domain theory. The description of L1 will be made using the function E : Expr → N. E [Const n] = n
E [e1 + e2 ] = E [e1 ] + E [e2 ]
In the case of L2 , to add variables to the language it is necessary to modify the evaluation function to take into account the context ς. The evaluation function will be E : Expr → State → N. E [V ar v ]ς = lkp v ς
The above change forces to modify previous definitions E [Const n]ς = n
E [e1 + e2 ]ς = E [e1 ]ς + E [e2 ]ς
In order to capture the denotational semantics of imperative commands, a new function is used C : Comm → (State → State). It means that the execution of a command denotes a state transformer function.
C [skip]ς = ς
C [x := e]ς = upd ς x (E [e]ς)
C [c1 ; c2 ]ς = C [c2 ](C [c1 ]ς)
The main problem of traditional denotational semantics is its lack of modularity and reusability. In fact, action and modular monadic semantics were proposed as an trying to solve the modularity problem. Denotational specifications are suitable to demonstrate properties [vHPR97] but there not many systems to obtain prototype interpreters from them. It is flexible and has been applied to a number of languages and paradigms, like Prolog [NF89], C [Pap98], XPath [Wad99], etc. 2.4. Axiomatic Semantics Axiomatic semantics [Hoa69] defines a set of inference rules that characterize the properties of the different language constructions. It has some popularity in academic environments with a number of textbooks that use it to verify program properties [Gri81, Bac86]. Its application to programming language specification is also described in [Win93, KS95]. In [HJ98] it is presented in a unifying framework with denotational and operational semantics. It uses assertions and Hoare triples based on preconditions and postconditions. An assertion is a predicate logic formula P that takes a boolean value in a context ς, which is denoted as [P] (ς) A Hoare triple is an expression {P }C{Q} where P and Q are assertions and C is a language statement. P is called precondition and Q is called postcondition. This formalism is normally used once the language has already been defined to check language properties. As an example, the inference rules for the imperative commands could be:
{P }skip{P }
{P (x/e)}x := e{P }
{P }c1 {Q} {Q}c2 {R} {P }c1 ; c2 {R}
Traditional axiomatic semantics has been succesfully applied to verify program properties and even to derive algorithms from specifications [Kal90, Gri81]. The specification describes the properties of different language constructs, so it should have no modularity problems. However, most of the presentations are made in a monolithic way, without taking into account modularity or reusability issues. 2.5. Algebraic Semantics Algebraic semantics appears from the works on algebraic datatype specifications made at the end of seventies. From a mathematical point of view, it is based on universal algebra, also called equational logic. Al algebraic specification allows to define an abstract mathematical structure with the properties it must satisfy. In a sense, axiomatic semantics can also be considered algebraic semantics [HJ98], although the evolution of both has been quite different. There are multiple algebraic specification languages like ASL [ST97],
Larch [CL94], OBJ, etc. In this section we will base our presentation on the algebraic semantics presented by Goguen et al. using the OBJ system [GM96] An algebraic specification consists of two parts: • The signature defines the sorts that are being specified and the operation symbols over those sorts with their functionalities. • The axioms are logical sentences that define the behaviour of the operations. A set of equations is normally used, establishing an equational logic. The following module defines an algebraic specification of integer values. obj Int is sort Int op 0 : → Int op succ : Int → Int op + : Int Int → Int vars x , y eq x + 0 = x eq x + succ y = succ (x + y) endo
The algebraic specification of the arithmetic expressions language is similar to the denotational semantics: obj Expr is pr Int subsort Int < Expr op + : Expr Expr → Expr op eval : Expr → Int var n e1 e2 : Expr eq eval n = n eq eval (e1 + e2 ) = eval e1 + eval e2 endo
— pr Int imports the integer specification — It means that an integer is an expression
The specification of variables requires an algebraic specification of the evaluation context. The specification with variables could be: obj VExpr is pr State, pr Expr subsort Var < Expr op eval : State Var → Int var x : Var , var ς : State eq eval ς x = lkp ς x endo
The previous evaluation functions are not valid because they did not take into account the context and should have to be modified. Imperative commands can be included as obj Comm is pr Expr , pr State sort Comm
op := : Var Expr → Comm op ; : Comm Comm → Comm op exec : State Comm → State var ς : State, vars c1 c2 : Comm, var x : Var , var e : Expr eq exec ς (x := e) = upd ς x (eval ς e) eq exec ς (c1 ; c2 ) = exec (exec ς c1 ) c2 endo
Algebraic semantics supports automatic verification of program properties. In principle, it seems possible to develop modular and reusable specifications. However, the specifications presented in [GM96] does not fulfill these requirements. The ASF+SDF framework [vdBea01, HK00] allows to obtain prototype interpreters from algebraic specifications. 2.6. Abstract State Machines Abstract State Machines define an algorithm using an abstraction of the working state and a set of transition rules over that state. They were originally called evolving algebras and were mainly developed by [Gur95]. Abstract state machines can be considered as an evolution of operational semantics that works with an abstraction level close to the algorithm that it is being specified. The description of the imperative language is inspired by the specification of Java [BS98] simplified to this example. The specification defines a tree that represents the abstract syntax of the program. The semantic specification traverses that tree. In each node, a given task is executed and then, the flow of control proceeds with the next task. A number of dynamic functions are used to represent the state of the computation. A dynamic function is a function that can be modified during the execution. In the case of arithmetic expressions, we use the dynamic function val : Expr → Int
that stores the intermediate value that will be returned. The transition rules will be if task is num then val (task ) := num proceed
if task is e1 + e2 then val (task ) := val (e1 ) + val (e2 ) proceed
To include variables, we add the dynamic function loc : Var → Value
that returns the actual value of a variable. The transition rule will be
if task is var then val (task ) := loc(var ) proceed The transition rules for imperative commands are if task is x := e then loc(x ) := val (e) proceed
if task is skip then proceed
In the case of the sequential structure c1 ; c2 the transition rule simply proceeds with c1 after having modified the execution function so that, when c1 ends, the execution continues with c2 . Abstract State Machines have succesfully been applied to specify different languages and paradigms. Moreover, the specifications are highly modular. As an example, the Java language specification is obtained in an incremental way from different modules [BS98]. There are also several systems that allow to execute abstract state machine specifications [B¨or02]. 2.7. Action Semantics Action semantics was proposed by P. Mosses [Mos92] with the goal to develop modular, readable and practical semantic specifications. The semantics of a language is described using actions that denote computations. From a syntactic point of view, action notation has some freedom looking as a kind of restricted natural language. Internally, that notation is transformed to structured operational semantics. There are primitive actions and action combinators. An action combinator takes one or more actions and generates a more complex action. the specification of the different types of actions is made using facets. There are several types of facets like the basic, functional, declarative, imperative and communicative facets, and each facet captures a given computational notion. The arithmetic language could be specified as (1) (2)
• evaluate :: Expr → action [giving Integer] evaluate [[ n:Numeral ]] = give n evaluate [[ E1 :Expr “+” E2 :Expr ]] = evaluate E1 and evaluate E2 then give sum(given Integer#1,given Integer#2)
To specify L2 we add identifiers that depend on the context (1)
evaluate [[ x : Ident ]] = give value stored in cell bound to x
The L3 language could be specified as (1)
• execute :: Comm → Action [completing execute [[ “skip” ]] = complete
storing]
(2)
(3)
execute [[ x:Ident “:=” E:Expr ]] =
give the cell bound to x and evaluate E then store the given Value#2 in the given cell#1 execute [[ C1 :Comm “;” C2 :Comm ]] = execute C1 and then execute C2
Although the descriptions look like a natural language, they are really made in a formal language with a very flexible syntax which allows identifiers with several words that are parsed without ambiguity. Action semantics achieves a good level of modularity given that actions are first class elements. However, it is not easy to combine different facets or to add new ones. Traditional action semantics does not allow the definition of reusable building blocks. There has been a recent proposal in [DM01] to define reusable action semantic specifications that has not yet been implemented. In this approach the combination of several features can lead to inconsistencies. Given that action notation is based on operational semantics, the verification of program properties is not an easy part. In [Wan97] action notation is defined using monad transformers. This line could also serve as a basis for a more thoroughly comparison between action semantics and modular monadic semantics. There are also several implementations of action semantics, like Actress [BMW92], OASIS [Orb94] and ABACO [MMV+ 01] that allow to obtain prototypes from specifications.
3. Modular Monadic Semantics The contents of this section are an overview of modular monadic semantics as presented in [LHJ95, LH96, Lia98]. 3.1. Monads and Monad Transformers We present a functional programmer’s definition of monad following [Wad92]. Definition 1 (Monad) A monad can be defined as a type constructor M with two operations returnM : α → Mα (=M ) : Mα → (α → Mβ) → Mβ which satisfy c =M returnM ≡c (returnM a) =M k ≡ k a (m =M f ) =M h ≡ m =M (λa.f a =M h) A monad M encapsulates the intuitive notion of a computation. In that way, a value of type M α can be considered as a computation M that returns a value of type α. The =M operation, which could be pronounced as bind, determines how the computations associated with monadic values are combined. returnM defines how a value α can be considered as a computation M α; it can be considered a trivial computation.
In Haskell, monads can be defined using constructor classes [Jon95] and it is also possible to use first-class polymorphism allowing to define monads as first class values [Jon97]. We use the following type class: class Monad m where return : α → m α (=) : m α → (α → m β) → m β () : m α → m α → m α m1 m2 = m1 =λ .m2 Example 1 The identity monad Id can be defined as newtype Id x = Id x instance Monad Id where return x = Id x (Id m) = f = f m It is possible to define special classes of monads for different notions of computations like state transformers, environment access, continuations, exceptions, Input/Output, backtracking, etc. Each class of monad adds some specific operations. Example 2 A monad m is an error handling monad if it has an operation err : String → m α that satisfies err x = f ≡ err x It can be defined using the following type class class (Monad m) ⇒ ErrMonad m where err : String → M α Example 3 A monad m is an environment reader monad for a given environment ρ if it has operations rdEnv : m ρ and inEnv : ρ → m α → m ρ which satisfy inEnv inEnv inEnv inEnv
ρ (return x ) ρ (m = λx .f x ) ρ rdEnv ρ0 (inEnv ρ m)
≡ return x ≡ inEnv ρ m = (λx .inEnv ρ (f x )) ≡ return ρ ≡ inEnv ρ m
In Haskell, an environment reader monad can be captured in the following multiparameter type class. class (Monad m) ⇒ EnvMonad ρ m where rdEnv : m ρ inEnv : ρ → m α → m α The above definition could be improved using functional dependencies as in [BHM00]
Example 4 A monad m is a state transformer monad for a given state ς if it has operations update : (ς → ς) → m ς, fetch : m ς and set : ς → m ς which satisfy: update f = update f 0 ≡ update (f . f 0 ) set ς = λx .fetch ≡ return ς fetch = λx .m ≡m In Haskell, it could be captured in the following type class, which could also be improved using functional dependencies. class (Monad m) ⇒ StateMonad ς m where update : (ς → ς) → m ς fetch : m ς fetch = update (λx .x ) set : ς → m () set ς = update (λ .ς) return () Example 5 A monad m is a backtracking monad [Hin98, Hin00a] if it has operations with the following types class (Monad m) ⇒ BackMonad m where failure : m α (d) : m α → m α → m α which satisfy failure d m m d failure m d (n d o) failure = k (m d n)= k
≡m ≡m ≡ (m d n) d o ≡ failure ≡ (m = k ) d (n = k )
When describing the semantics of a programming language using monads, the main problem is the combination of different classes of monads. In general, it is not possible to compose two monads to obtain a new monad [JD93]. Nevertheless, a monad transformer T can transform a given monad m into a new monad T m that has new operations and maintains the operations of m. The idea of monad transformer is based on the notion of monad morphism that appeared in [Mog89] and was later proposed in [LHJ95]. Definition 2 (Monad transformer) A monad transformer is a type constructor T with an associated operation lift : m α → T m α that transforms a monad m into a new monad T m and satisfies lift · return ≡ return lift (m = k ) ≡ (lift m) = (lift · k )
In Haskell, a monad transformer can be defined using the following multi-parameter type class. class (Monad m) ⇒ MonadT T m where lift : m α → T m α When defining a monad transformer T , it is necessary to specify the operations return, (=), lift and the specific operations that the monad transformer adds. Example 6 TErr transforms a monad m into an exception handling monad TErr m. newtype TErr m α = E { unE : m (k α String) } return x = E (return (Left x )) m = f = E ((unE m) = λy. case y of Left v → unE (f v ) Right e → return (Right e) lift m = E (m = λx .return (Left x )) err msg = E (return (Right msg)) Example 7 Given an environment ρ, TEnv transforms a monad m into an environment reader monad TEnv ρ m. newtype TEnv ρ m α return x m = f lift m rdEnv inEnv ρ x
= V { unV : ρ → m α } = V (λρ.return x ) = V (λρ → unV m ρ=λv . unV (f v ) ρ) = V (λρ.m=return) = V return = V (λ .x ρ)
Example 8 TState transforms a monad m into a state transformer monad TState ς m for a given state ς. newtype TState ς m α return x m = f lift m update f fetch set ς
= S { unS : ς → m (α, ς) } = S (λς.return (x , ς)) = S (λς → unS m ς=λ(v , ς 0 ). unS (f v ) ς 0 ) = S (λς.m=λx . return(x , ς) ) = S (λς.return (ς, f ς)) = update (λς.ς) = update (λς 0 .ς)
Example 9 TBack transforms a monad M into a backtracking monad newtype TBack m α return x m = f lift m failure m d n
=B =B =B =B =B =B
{ unB : ∀β.((α → m β → m β) → m β → m β)} (λκ.κ x ) (λκ.(unB m) (λv .unB (f v ) κ)) (λ κ f → m=λx → κ x f ) (λκ → (λx → x )) (λκ f → (unB m) κ ((unB n) κ f ))
The above definition has been taken from [Hin98] and uses rank-2 polymorphic types. In general, the definition of monad transformers is not straightforward because there can be some interactions between the operations of the different monads. These interactions are considered in more detail in [Lia98, LH96, LHJ95] and in [Hin00a] it is shown how to derive the backtracking monad transformer from its specification. 3.2. Extensible domains As has been shown in [LHJ95], extensible domains can be defined in Haskell using multiparameter type classes. Although this feature has been implemented in the main Haskell distributions, it is not standard yet. We define class α ⊆ β where ↑: α → β ↓: β → α
— injection — projection, in practice, it is defined as a partial function
and the following instance declarations instance α ⊆ (αkβ) where ↑x =Lx ↓ (L y) = y
instance (α ⊆ β) ⇒ α ⊆ (χkβ) where ↑x = R (↑ x ) ↓ (R x ) = ↓ x
Example 10 The function inc inc : (Int ⊆ v ) ⇒ v → v inc v = ↑ (↓ v + 1) will increment values in any domain that contains integers. 3.3. Simple Language Specification using Modular Monadic Semantics To specify the semantics of a programming language using modular monadic semantics we define a semantic function from the abstract syntax into a computational monad. We define the abstract syntax as: data Expr = Expr + Expr | Num Int The evaluation function does not need to establish any constraint on the monad. :: (Monad m, Int ⊆ v ) ⇒ Expr → m v E[ ] E [e1 + e2 ] = E [e1 ]=λv1 . E [e2 ]=λv2 . return (↑ (↓ v1 + ↓ v2 )) E [N um n] = return (↑ n) When we add variables, we change the abstract syntax
data Expr = ... | Var Name
— previous definitions
Now, the computational monad must be an environment reader as the value of a variable if taken from the environment. E[ ] :: (EnvMonad State m, Int ⊆ v ) ⇒ Expr → m v E [N um n] = return (↑ n) E [e1 + e2 ] = E [e1 ]=λv1 . E [e2 ]=λv2 . return (↑ (↓ v1 + ↓ v2 ) E [V ar x] = rdEnv =λς. return(lkp ς x ) Notice that the definitions of arithmetic expressions did not have to be modified. However, although the addition of variables was made in an incremental way, there was no notion of an arithmetic expressions block and the evaluation function had to be modified. Finally, if we want to add imperative commands, we define a new abstract syntax datatype data Comm = Comm ; Comm | Name := Expr | Skip The execution of commands can be specified as C[ ] C [skip] C [c1 ; c2 ] C [x := e]
: (StateMonad State m) ⇒ Comm → m () = return () = C [c1 ]=λ .C [c2 ] = E [e]=λv . fetch=λς. set (upd ς x v )
The type of the execution function reflects the intuitive understanding of commands. A command executes some actions transforming a state (thus the computational monad must be a state transformer) and returns nothing. In this simple language there was no need to separate the environment and the state, so the environment reader monad and the state transformer monad worked on the same datatype State. This was possible because any state transformer monad is also an environment reader monad. In the following sections, we will use a more general approach. We will assume that we have some utility modules implementing Heaps and Tables. Heap α will be an abstract datatype addressed by locations of type Loc with the following operations: allocH : α → Heap α → (Loc, Heap α) lkpH : Loc → Heap α → α
— allocate new values — lookup
updH
: Loc → α → Heap α → Heap α
— update
Table α is an abstract datatype representing tables addressed by values of type Name with the following operations: lkpT : Name → Table α → α updT : Name → α → Table α → Table α
— lookup — update
The environment will be modelled as a table and the state as a heap, both storing computations of type Comp Value for different monads of type Comp and values of type Value.
type Env = Table (Comp Value) type State = Heap (Comp Value)
4. Generic Programming Concepts 4.1. Functors, Algebras and Catamorphisms Definition 3 (Functor) A functor f is a type constructor that transforms values of type α into values of type f α and a function fmap : (α → β) → f α → f β which satisfies fmap id ≡ id fmap (f . g) ≡ fmap f . fmap g In Haskell, a functor can be captured by the following type class class Functor f where fmap : (α → β) → f α → f β A recursive datatype can be defined as the fixed point of a non-recursive functor that captures its shape. In Haskell, the fixed point of a functor F can be defined as newtype µf = In { f (µf) } Example 11 The inductive datatype Expr defined in example 3.3. can be defined as the fixpoint of the functor E data E x = x + x | Num Int where the instance declaration can be defined as: instance Functor E
where fmap f (Num n) = n fmap f (e1 + e2 ) = f e1 + f e2 Once we have the shape functor E, we can obtain the recursive datatype as the fixed point of E type Expr = Fix E The term 3 + 4 can be represented as In ((In (Num 3)) + (In (Num 4))) : Expr Definition 4 (F-algebra) Given a functor F, an F-algebra is a function ϕF : F α → α where α is called the carrier. Definition 5 (Homomorphism) A homomorphism between two F-algebras ϕ : F α → α and ψ : F β → β is a function h : α → β which satisfies h . ϕ ≡ ψ . fmap h fα f map h
ϕ
fβ
/α
ψ
h
/β
It is possible to define a category with F-algebras as objects and homomorphisms between F-algebras as morphisms. An initial object in this category exists provided F is a polynomial functor [Mal90]. This initial object corresponds with the type constructor In : F µF → µF, which means that, for any F-algebra ϕ : F α → α there is a unique homomorphism ([ϕ]) : µF → α satisfying equation 5. ([ϕ]) is called fold or catamorphism and satisfies a number of calculational properties [BJJM99, BdM97, MFP91]. It can be represented pictorially by the following diagram. f(µf) f map ([ϕ])
In
([ϕ])
fα
/ µf
ϕ
/α
Definition 6 (catamorphism) A fold or catamorphism can be defined as ([ ]) : (Functor f) ⇒ (fα → α) → µf → α) ([ϕ]) = ϕ . map (([ϕ])) . out where out : µf → f µf out (In x ) = x
Example 12 We can obtain a simple evaluator for arithmetic expressions defining a Ealgebra whose carrier is the type m v , where m is, in this case, any kind of monad. ϕE : (Monad m, Int ⊆ v ) ⇒ T (m v ) → (m v ) ϕE (Num n) = return (↑ n) ϕE (c1 + c2 ) = c1 =λv1 . c2 =λv2 . return ↑ (↓ v1 + ↓ v2 ) The interpreter of arithmetic expressions is obtained as a catamorphism InterExpr : (Monad m, Int ⊆ v ) ⇒ Expr → m v InterExpr = ([ϕE ]) Definition 7 (Sum of functors) The sum of two functors f and g, denoted by f ⊕ g can be defined as newtype (f ⊕ g) x = S (Either (f x ) (g x )) where the instance declaration is instance (Functor f, Functor g) ⇒ Functor (f ⊕ g) where fmap f (S (Left x )) = S (Left (fmap f x )) fmap f (S (Right x )) = S (Right (fmap f x )) Using the sum of two functors, it is possible to extend recursive datatypes. Example 13 We can define a new pattern functor for variables as data V e = Var Name The composed recursive datatype of arithmetic expressions and variables can easily be defined as type Expr 0 = µ(T ⊕ V) In order to obtain an (f ⊕ g)-algebra from an f-algebra ϕ and a g-algebra ψ we define the operator ⊕ as: ⊕ : (f α → α) → (g α → α) → (f ⊕ g)α → α (ϕ ⊕ ψ) (S (Left x )) = ϕ x (ϕ ⊕ ψ) (S (Right x )) = ψ x
Example 14 We can add variables to the arithmetic expressions language without modifying the existing definitions. We only need to define the corresponding V-algebra as: ϕV : ( EnvMonad State m, Int ⊆ v ) ⇒ V (m v ) → m v ϕV (Var x ) = rdEnv = lkpT x A new evaluator for expressions is automatically obtained by means of a catamorphism over the (E ⊕ V)-algebra. InterExpr 0 : (EnvMonad State m, Int ⊆ v ) ⇒ Expr 0 → m v InterExpr 0 = ([ϕE ⊕ ϕV ]) 4.2. n-sorted algebras and catamorphisms The abstract syntax of a programming language is usually composed from several mutually recursive categories. It is possible to extend the previous definitions to handle manysorted algebras. The following presentation has been mainly inspired by Fokkinga’s thesis [Fok92]. Some related work appears also in [Mei92, IHT98] and in [LCLG01, Lab01] it is presented in more detail for two-sorted algebras. Definition 8 (n-Functor) An n-Functor f n is a type constructor that transforms tuples of types (α1 . . . αn ) into values of type f n α1 . . . αn and a function mapfnn : (α1 → β1 ) → . . . → (αn → βn ) → f n α1 . . . αn → f n β1 . . . βn which preserves identities and composition. A tuple of n-functors (f1n . . . fnn ) will be denoted as f. Definition 9 (Fixed point of n-Functors) Given a tuple of n-Functors f, the fixed point of f is a tuple of values (µn1 f . . . µnn f) which can be defined as data µn1 f = In1n (f1n µn1 f . . . µnn f) ··· data µnn f = Innn (fnn µn1 f . . . µnn f) Definition 10 (n-sorted f-algebra) Given a tuple of n-functors f, a n-sorted f-algebra ϕ is a tuple of functions (ϕ1 . . . ϕn ) with the following types ϕ1 : f1n α1 . . . αn → α1 ··· ϕn : fnn α1 . . . αn → αn The types α1 . . . αn are called the sorts of the n-sorted f-algebra. Definition 11 (Homomorphism between n-sorted f-algebras) Given two n-sorted falgebras ϕ and ψ with the corresponding sorts (α1 . . . αn ) and (β1 . . . βn ), an homomorphism between ϕ and ψ is a tuple of functions (h1 : α1 → β1 . . . hn : αn → βn ) such that h1 .ϕ1 = ψ1 .mapnf1n h1 . . . hn ... hn .ϕn = ψn .mapnfnn h1 . . . hn
(1)
Each equation can be represented by the following diagram fin α1 . . . αn mapn h ...hn fn 1 i
ϕi
fin β1 . . . βn
/ αi
ψi
hi
/ βi
As in the case of catamorphisms, it is possible to define a category where the objects are n-sorted f-algebras and the morphisms are homomorphims between n-sorted f-algebras. An initial object in this category is represented by (In1n : f1n µn1 f . . . µnn f → µn1 f, . . . , Innn : fnn µn1 f . . . µnn f → µnn f) This means that, for any n-sorted f-algebra ϕ with sorts (α1 . . . αn ), there are n functions ([ϕ])n1 : µn1 f → α1 ... ([ϕ])nn : µnn f → αn satisfying equation 1. Each function ([ϕ])ni is called an n-catamorphism and follows the diagram fin µn1 f . . . µnn f n mapn ([ϕ])n 1 ...([ϕ])n fn i
Inn i
/ µn f i ([ϕ])n i
fin α1 . . . αn
ϕi
/ αi
In Haskell, the n-catamorphisms can be defined as ([ ])ni : (f1n α1 . . . αn → α1 ) → · · · → (fnn α1 . . . αn → αn ) → µni f → αi ([ϕ])ni = ϕi . mapfnin ([ϕ])n1 . . . ([ϕ])nn . outin where outin : µni f → fin µn1 f . . . µnn f outin (Inin x ) = x Definition 12 (Sum of n-functors) The sum of two n-functors f n and gn , denoted as f n ⊕n gn , can be defined as type (f n ⊕n gn ) α1 . . . αn = Either (f n α1 . . . αn ) (gn α1 . . . αn ) where the mapfnn ⊕n gn function is defined as mapfnn ⊕n gn f1 . . . fn (Left x ) = Left (mapfnn f1 . . . fn x ) mapfnn ⊕n gn f1 . . . fn (Right x ) = Right (mapgnn f1 . . . fn x )
The following operator ⊕ni will be useful when we want to extend two independent specifications. ⊕ni : (f n α1 . . . αn → αi ) → (gn α1 . . . αn → αi ) → (f n ⊕n gn )α1 . . . αn → αi ϕ ⊕ni ψ (Left x ) = ϕ x ϕ ⊕ni ψ (Right x ) = ψ x The following definition allows to extend a given functor to an n-functor. We may also define a more general extension from m-functors to n-functors for (m < n) in a similar way. Definition 13 (Functor extension) The extension of a functor f, to an n-functor fin is defined as data fin α1 . . . αn = Ein f αi where the mapfnin function is defined as mapfnin f1 . . . fn (Ein x ) = fi x ni converts an f-algebra ϕ : f α → α in a n-sorted fin -algebra. : (f α → α) → fin α1 . . . αn → αi ni n n i ϕ (Ei x ) = ϕ x Example 15 The L3 language defined in section 2.1. had two syntactic catogories, expressions and commands. We can capture the shape of imperative commands using the followint 2-functor. data C e c = c ; c | Name := e | skip The abstract syntax of L3 is obtained as the following fixed-point type Comm = µ22 ((E ⊕ V)21 , C) The semantic specification of commands is described using the following 2-algebra ϕC : ( StateMonad State m, Int ⊆ v ⇒ C(m v ) (m ()) → m () ϕC (c1 ; c2 ) = c1 c2 ϕC (x := e) = e=λv . fetch=λς. set (updH x v ς) Now, we can obtain an interpreter for the L3 language as a 2-catamorphism InterComm : ( StateMonad State m, Int ⊆ v ) ⇒ Comm → m () InterComm = ([21 (ϕE ⊕ ϕV ), ϕC ])22 In practice, it is not necessary to explicitily write the algebra subscripts because Haskell overloading mechanism automatically detects which is the involved algebra.
5. Reusable Monadic Semantics Reusable monadic semantics is a combination of modular monadic semantics with generic programming concepts. The examples given in the previous section presented the specifications of the languages defined in section 2.1.. As the reader could check, the specifications have been made in an independent and incremental way. Moreover, an interpreter was automatically obtained from the specifications. In this section we show that this approach can be applied to the specification of different languages and paradigms. To that end, we incrementally add semantic building blocks like multiplications and divisions, local declarations, abstractions and typical control structures. As a final example, we define a prolog-like language and extend it with arithmetic expressions. 5.1. Multiplications and divisions We can extend the simple L3 language with multiplications and divisions defining the following functor. data M e = e × e | e ÷e The semantic specification could be ϕM : (ErrMonad m, Int ⊆ v ) ⇒ M (m v ) → (m v ) ϕM (c1 × c2 ) = c1 =λv1 . c2 =λv2 . return ↑ (↓ v1 ∗ ↓ v2 ) ϕM (c1 ÷ c2 ) = c1 =λv1 . c2 =λv2 . if ↓ v2 == 0 then err “division by zero” else return ↑ (↓ v1 / ↓ v2 ) The specification takes into account the possibility of errors declaring that the underlying monad must be an instance of ErrMonad . 5.2. Local declarations and abstractions In this section we add local declarations and abstractions using a call by value semantics. In [LLCC01] we present a functional programming language specification with different evaluation mechanisms (call by value, by name and by need). The abstract syntax is defined using the following functor: data A e = λ Name e | e @e | Let Name e e
— Lambda abstraction — Application — Local declaration
The semantic specification could be: ϕA
: ( EnvMonad Env m
, StateMonad State m , (m v → m v ) ⊆ v ) ⇒ A (m v ) → m v ϕA (λ x c) = rdEnv = λρ. return (↑ (λm.m =λv .inEnv (updT x (return v ) ρ) c)) ϕA (c1 @ c2 ) = c1 = λv . rdEnv = λρ. (↓ v ) (inEnv ρ c2 ) ϕA (Let x c1 c2 ) = fetch = λς. rdEnv = λρ. let (loc, ς 0 ) = allocH c1 ς ρ0 = updT x (fetch = lkpH loc) ρ in set ς 0 inEnv ρ0 c1 = λv . update (updH loc (return v )) inEnv ρ0 c2 A simple lambda calculus language with arithmetic expressions could be defined as type Lambda = µ(A ⊕ V ⊕ E ⊕ M) And the interpreter is automatically obtained as: InterLambda = ([ϕA ⊕ ϕV ⊕ ϕE ⊕ ϕM ])
5.3. Control structures In this section we add typical control structures of programming languages like conditionals and repetitions. data W e c = If e c c | While e c The abstract syntax of the imperative language could be obtained as: type Comm 0 , µ22 ((E ⊕ M ⊕ V)21 , C ⊕2 W) The semantic specification could be ϕW : (Monad m) ⇒ W(m v ) (m ()) → m () ϕW (If e c1 c2 ) = e=λv . if v 6= 0 then c1 else c2
ϕW (While e c) = loop where loop = e=λv . if v then c loop else return() The interpreter of the new imperative language is InterComm 0 : Comm → m () InterComm 0 = ([21 (ϕT ⊕ ϕF ⊕ ϕV ), ϕS ⊕2 ϕW ])22 The above specifications can be extended to handle objects, classes and inheritance [LCLC02, Lab01]. 5.4. Logic Programming Language In this section we will define the semantics of a pure logic programming language. The material in this section has also been presented in [LCLC01a, LCLC01b]. Prolog terms are defined as Term = C Name | V Name | F Name [Term]
— Constants — Variables — Compound terms
Facts and rules will be represented as local declarations, leaving the goal as an executable expression. We will use the functor P to capture the abstract syntax of the language Our abstract syntax assumes all predicates to be unary, this simplifies the definition of the semantics without loss of generality. P e = Def Name Name e e | e ∧ e | e ∨ e | ∃(Name → e) | call Name Term | Term $ Term | ?Name (Name → e)
— Definitions — Conjunction — Disjunction — Free variables — Predicate call — Unification — Goal
The Prolog language is defined as the fixed point of P Prolog , µP Example 16 The Prolog program p(a). p(f (x )) ← p(x ) with the goal ? p(x ) could be codified as Def p v (v $ a ∨ ∃(λx .v $ f (x ) ∧ call p x )) (?x (λx .call p x ))
In this sample language, we need to handle errors, backtracking, environment access and to modify a global state. The global state in this simple case is only needed as a supply of fresh variable names. The resulting monad will be Comp = TBack (TEnv Env (TState State (TErr IO) we used the predefined IO monad as the base monad to facilitate the communication of solutions to the user. We also used the domains Subst Database Env State
, , , ,
Name → Term Name → (Name, Comp Subst) (Database, Subst) Int
— Substitutions — Clause Definitions — Environment — Global state
The semantic specification of the Prolog language consists of a P-algebra whose carrier is the computational structure. ϕP : P (Comp Value) → Comp Value ϕP (Def p x e1 e2 ) = rdEnv =λ(ρ, σ). inEnv (ρ {p/(x , e1 )}, σ) e2 ϕP (e1 ∧ e2 ) = rdEnv =λ(ρ, σ). e1 =λσ 0 . inEnv (ρ, σ 0 ) e2 ϕP (e1 ∨ e2 ) = rdEnv =λ(ρ, σ). inEnv (ρ, σ) e1 d inEnv (ρ, σ) e2 ϕP (∃ f ) = update (+1)=λn.f (mkFree n) ϕP (call p t) = rdEnv =λ(ρ, σ). let (x , m) = ρ (p, t) in unifyS (C x ) t σ=λσ 0 .inEnv (ρ, σ 0 ) m ϕP (t1 $ t2 ) = rdEnv =λ(ρ, σ). unifyS t1 t2 σ ϕP (? x f ) = update (+1) =λn. f (mkFree n) =λσ. putAnswer x (σ v )) =λ . return σ The following auxiliary definitions have been used • mkFree : Int → Name, creates a new name • putAnswer : Name → Term → Comp (), writes the value of a variable and asks the user for more answers • unifyS : Term → Term → Subst → Comp Subst, unifies two terms The Prolog interpreter is automatically obtained as a catamorphism InterProlog : Prolog → Comp Subst InterProlog = ([ϕP ])
5.5. Logic Programming Language with Arithmetic Predicates The Prolog predicate (is) opens a new semantic world in the language as it implies the arithmetic evaluation of one of its arguments. Other specifications of Prolog [NF89, BR94] often avoid this predicate as it can interfere with the understanding of the particular aspects of Prolog. In our approach, it is possible to reuse the independent specifications of pure logic programming and arithmetic evaluation and combine them to form a new language. As we are going to use two different categories, we define the bifunctor I g e , Term is e and the semantic specification ϕI : I (Comp Subst) (Comp Int) → Comp Subst ϕI (t is e) = e=λv . rdEnv =λ(ρ, σ). unifyS t (cnv v ) σ where cnv : Int → Term converts an integer into a constant term. The extended language can be defined as Prolog + , µ1 (P21 1 I) (E ⊕ M)22 and the corresponding interpreter is obtained as a bicatamorphism InterProlog + : Prolog + → Comp Subst InterProlog + = ([21 ϕP 1 ϕI , 22 (ϕE ⊕ ϕM )])1
6. Conclusions In the table 1 we present our assessment of the different semantic specification techniques. When the technique X has a feature Y , the cell (X , Y ) contains the symbol ↑ whereas if it partially supports the feature, we use ≈. • Non ambiguity (NAM). Of course, natural language is ambiguous while all the other approaches solve that problem using a mathematical formalism. • Semantic Modularity (MOD). Semantic modularity is achieved by natural language due to its lack of rigour, which allows to give some fuzzy (yet modular) descriptions that adapt to new features. Traditional formalisms do not solve the problem (they were not invented to solve that problem) and it may be one of the reasons of their limited popularity [Wat96]. Abstract state machines solve the problem using a specializitation of the abstract state. Action semantics solves specializing the facets and modular and reusable monadic semantics solve it specializing the underlying monad.
Natural Lang. Struct. Operat. Sem. Natural Sem. Denotational Sem. Axiomatic Sem. Algebraic Sem. Abst. State Mach. Action Sem. Mod. Monadic Sem. Reusable Monadic Sem.
NAM MOD ↑ ↑ ↑ ↑ ≈ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑
REU DEM ↑ ≈ ≈ ↑ ↑ ≈ ↑ ≈ ≈ ≈ ≈ ↑ ↑ ↑
PRO ≈ ↑ ≈ ↑ ↑ ↑ ≈ ↑
REA FLE ≈ ↑ ≈ ↑ ≈ ↑ ↑ ≈ ≈ ≈ ≈ ↑ ≈ ↑ ↑
EXP ↑ ↑ ↑ ≈ ↑ ≈ ↑ ≈
Table 1: Comparison between semantic specification approaches
• Reusable semantic components (REU). Natural language, for the same reason, allows the definition of reusable semantic components (sometimes, we just need to cut and paste). Traditional formalisms do not attack that problem. Algebraic semantics contains good reusability mechanisms, although the specifications made in [GM96] are monolythic. In the action semantics approach, there has been some recent developments [DM01]. In that approach the combination of features may lead to inconsistencies. Reusable monadic semantics solves the reusability problems using generic programming concepts, it does not produce as the different blocks combined belong to different categories. • Demonstration (DEM). Of course, natural language does not allow to verify program properties. In the case of the other approaches, the more operational they are, the less they allow it. Algebraic and axiomatic semantics facilitate formal reasoning as they use axioms that are independent of a particular implementation. In the same way, denotational semantics describes the language using abstract mathematical concepts that allow this demonstrations. In the case of Action Semantics, the action notation is described in operational semantics. An attempt to define action notation using monad transformers was presented in [Wan97]. Modular and reusable monadic semantics are a combination between denotational and algebraic semantics, as language concepts are defined using monads, which have an observationally described behaviour. • Prototypes (PRO). Operational systems facilitate prototype development. With traditional axiomatic semantics it is more difficult as it is mainly based on the separation between the implementation and the axioms. In the case of algebraic semantics, which is based on equational logic, it is easier to develop prototypes using rewriting systems. For the same reason, modular and reusable monadic semantics allow to automatically obtain prototype interpreters. • Readability (REA). Action semantics has given a lot of emphasis on readability. It may be interesting to adopt a similar syntax for other approaches. • Flexibility (FLE). The denotational and operational approaches are easily applicable to different paradigms. On the other hand, traditional axiomatic semantics has a stronger dependency on the paradigm that it is applied to. With this tutorial, we also expect to show that reusble monadic semantics is applicable to different
paradigms. • Experience (EXP). Traditional approaches, specially those based on operational techniques, have been applied to describe real languages. In theory, reusable monadic semantics could also be applied, but we are not aware of any work in that direction. With regard to the implementation, we have developed a Language Prototyping System in Haskell. The implementation offers an interactive framework for language testing and is based on a domain-specific meta-language embedded in Haskell. This approach offers easier development and the fairly good type system of Haskell. For example, Haskell type class system offers a very flexible way to combine different semantic blocks. In fact, it can be considered as a full constraint language on the type level [GSS00]. Nevertheless, there are some disadvantages like the mixture of error messages between the host language and the metalanguage, Haskell dependency and some type system limitations. We are currently planning to develop an independent meta-language following [Mog97]. Some of Haskell type system limitations that we would like to incorporate in the metalanguage are first-class polymorphism [Jon97], extensible records and variants [GJ96], polytypism [Hin00b] and dependent types [Aug98].
References Lennart Augustsson. Cayenne - a language with dependent types. In International Conference on Functional Programming, pages 239–250, 1998. R. Backhouse. Program Construction and Verification. Prentice Hall International, Englewood Cliffs, NJ, 1986. R. Bird and Oege de Moor. Algebra of Programming. Prentice Hall, 1997. N. Benton, J. Hughes, and E. Moggi. Monads and effects. In International Summer School On Applied Semantics APPSEM’2000, Caminha, Portugal, 2000. Roland Backhouse, Patrik Jansson, Johan Jeuring, and Lambert Meertens. Generic programming - an introduction. In S. Swierstra, P. Henriques, and Jose N. Oliveira, editors, Advanced Functional Programming, volume 1608 of Lecture Notes in Computer Science. Springer, 1999. D. F. Brown, H. Moura, and D. A. Watt. Actress: an action semantics directed compiler generator. In 4th International Conference on Compiler Construction, volume 641 of LNCS, pages 95–109. Springer-Verlag, 1992. E. B¨orger. The origins and the development of the asm method for high level system design and analysis. Journal of Universal Computer Science, 8(1):2–74, 2002. E. B¨orger and D. Rosenzweig. A mathematical definition of full prolog. Science of Computer Programming, 1994. E. B¨orger and W. Schulte. Programmer friendly modular definition of the semantics of java. In J. Alver-Foss, editor, Formal Syntax and Semantics of Java, LNCS. Springer, 1998.
Yoonsik Cheon and Gary T. Leavens. A quick overview of Larch/C++. Journal of ObjectOriented Programming, 7(6):39–49, 1994. Kyung-Goo Doh and Peter D. Mosses. Composing programming languages by combining action-semantics modules. In Mark van den Brand and Didier Parigot, editors, Electronic Notes in Theoretical Computer Science, volume 44. Elsevier Science Publishers, 2001. Maarten M. Fokkinga. Law and Order in Algorithmics. PhD thesis, University of Twente, February 1992. Benedict R. Gaster and Mark P. Jones. A Polymorphic Type System for Extensible Records and Variants. Technical Report NOTTCS-TR-96-3, Department of Computer Science, University of Nottingham, November 1996. Joseph A. Goguen and Grant Malcolm. Algebraic Semantics of Imperative Programs. Foundations in Computer Science. The MIT Press, 1996. D. Gries. The Science of Programming. Springer-Verlag, New York, 1981. K. Glynn, M. Sulzmann, and P.J. Stuckey. Type classes and constraint handling rules. Technical report, 2000. Y. Gurevich. Evolving algebra 1993: Lipari guide. In E. B¨orger, editor, Specification and Validation Methods, pages 9–36. Oxford University Press, 1995. Ralf Hinze. Prological features in a functional setting — axioms and implementations. In Masahiko Sato and Yoshihito Toyama, editors, Third Fuji International Symposium on Functional and Logic Programming (FLOPS’98), Kyoto University, Japan, pages 98–122, Singapore, New Jersey, London, Hong Kong, April 1998. World Scientific. Ralf Hinze. Deriving backtracking monad transformers. In Roland Backhouse and Jose N. Oliveira, editors, Proceedings of the 2000 International Conference on Functional Programming, Montreal, Canada, September 2000. Ralf Hinze. Polytypic values possess polykinded types. In Roland Backhouse and J.N. Oliveira, editors, Proceedings of the Fifth International Conference on Mathematics of Program Construction (MPC 2000), volume 1837 of Lecture Notes in Computer Science, pages 2–27. Springer Verlag, July 2000. C. A. R. Hoare and H. Jifeng. Unifying Theories of Programming. Prentice Hall, 1998. J. Heering and P. Klint. Semantics of programming languages: A tool-oriented approach. ACM SIGPLAN Notinces, 2000. C. A. R. Hoare. An axiomatic basis for computer programming. Communications of the ACM, 12(10):576–580, October 1969. H. Iwasaki, Z. Hu, and M. Takeichi. Towards manipulation of mutually recursive functions. In 3rd. Fuji International Symposium on Functional and Logic Programming (FLOPS’98), Kyoto, Japan, 1998. World Scientific. Mark P. Jones and L. Duponcheel. Composing monads. YALEU/DCS/RR 1004, Yale University, New Haven, CT, USA, 1993.
Mark P. Jones. A system of constructor classes: overloading and implicit higher-order polymorphism. Journal of Functional Programming, 5(1):1–35, January 1995. Mark P. Jones. First-class Polymorphism with Type Inference. In Proceedings of the Twenty Fourth Annual ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages, Paris, France, January 15-17 1997. Bill Joy, Guy Steele, Jame Gosling, and Gilad Bracha. The Java Language Specification. Addison-Wesley, 2 edition, 2000. G. Kahn. Natural semantics. In Proceedings of the Symposium on Theoretical Aspects of Computer Science, volume 247, pages 22–39. Springer-Verlag, 1987. A. Kaldewaij. Programming: the derivation of algorithms. International Series in Computer Science. Prentice Hall, 1990. B. L. Kurtz K. Slonneger. Formal Syntax and Semantics of Programming Languages. Addison-Wesley, 1995. Jose E. Labra. Modular Development of Language Processors from Reusable Semantic Specifications. PhD thesis, Dept. of Computer Science, University of Oviedo, 2001. In spanish. J. E. Labra, J. M. Cueva, M. C. Luengo, and A. Cernuda. Reusable monadic semantics of logic programs with arithmetic predicates. In Lu´ıs Moniz Pereira and Paulo Quaresma, editors, APPIA-GULP-PRODE 2001, Joint Conference on ´ Declarative Programming, Evora, Portugal, September 2001. Universidade de Evora. J. E. Labra, J. M. Cueva, M. C. Luengo, and A. Cernuda. Specification of logic programming languages from reusable semantic building blocks. In International Workshop on Functional and (Constraint) Logic Programming, number 2017, Kiel, Germany, September 2001. Christian-Albrechts-Universit¨at Kiel. J. E. Labra, J. M. Cueva, M. C. Luengo, and A. Cernuda. Reusable monadic semantics of object oriented programming languages. In 6th Brazilian Symposium on Programming Languages, pages 86–100, Rio de Janeiro, Brazil, Jun 2002. PUC-Rio. J. E. Labra, J. M. Cueva, M. C. Luengo, and B. M. Gonz´alez. A language prototyping tool based on semantic building blocks. In Eight International Conference on Computer Aided Systems Theory and Technology (EUROCAST’01), volume 2178 of Lecture Notes in Computer Science, Las Palmas de Gran Canaria – Spain, February 2001. Springer Verlag. Sheng Liang and Paul Hudak. Modular denotational semantics for compiler construction. In Programming Languages and Systems – ESOP’96, Proc. 6th European Symposium on Programming, Link¨oping, volume 1058 of Lecture Notes in Computer Science, pages 219–234. Springer-Verlag, 1996.
Sheng Liang, Paul Hudak, and Mark P. Jones. Monad transformers and modular interpreters. In 22nd ACM Symposium on Principles of Programming Languages, San Francisco, CA. ACM, January 1995. Sheng Liang. Modular Monadic Semantics and Compilation. PhD thesis, Graduate School of Yale University, May 1998. J.E. Labra, M.C. Luengo, J.M. Cueva, and A. Cernuda. LPS: A language prototyping system using modular monadic semantics. In Mark van den Brand and Didier Parigot, editors, Electronic Notes in Theoretical Computer Science, volume 44. Elsevier Science Publishers, 2001. Grant Malcolm. Data structures and program transformation. Science of Computer Programming, 14:255–279, 1990. Erik Meijer. Calculating Compilers. PhD thesis, University of Nijmegen, February 1992. E. Meijer, M. M. Fokkinga, and R. Paterson. Functional programming with bananas, lenses, envelopes and barbed wire. In Functional Programming and Computer Architecture, pages 124–144. Springer-Verlag, 1991. L. Menezes, H. Moura, M. Vieira, W. Cansanc¸a¯ o, F. Lima, and L. Ribeiro. An action semantics integrated development environment. In M. van der Brand, M. Mernik, and D. Parigot, editors, First Workshop on Language Descriptions, Tools and Applications, pages 219–222, Genova, Italy, Apr 2001. Eugenio Moggi. An abstract view of programming languages. Technical Report ECSLFCS-90-113, Edinburgh University, Dept. of Computer Science, June 1989. Lecture Notes for course CS 359, Stanford University. E. Moggi. Metalanguages and applications. In A. M. Pitts and P. Dybjer, editors, Semantics and Logics of Computation, Publications of the Newton Institute. Cambridge University Press, 1997. Peter D. Mosses. Action Semantics. Cambridge University Press, 1992. Peter D. Mosses. Theory and practice of action semantics. In 21st Int. Symp. on Mathematical Foundations of Computer Science, volume 1113, pages 37–61, Cracow, Poland, Sept 1996. Lecture Notes in Computer Science, SpringerVerlag. P. Mosses. Foundations of modular SOS. Technical Report RS-99-54, BRICS, Dept. of Computer Science, University of Aarhus, 1999. T. Nicholson and N. Foo. A denotational semantics for prolog. ACM Transactions on Programming Languages and Systems, 11(4):650–665, 1989. P. Orbaek. Oasis: An optimizing action-based compiler generator. In Proceedings 5th International Conference on Compiler Construction, volume 786 of LNCS, pages 1–15. Springer-Verlag, 1994. Nikolaos S. Papaspyrou. A Formal Semantics for the C Programming Language. PhD thesis, National Technical University of Athens, Department of Electrical and Computer Engineering, February 1998.
Gordon D. Plotkin. A structural approach to operational semantics. Technical Report DAIMI FN-19, Computer Science Department, Aarhus University, Aarhus, Denmark, 1981. D. Sannella and A. Tarlecki. Essential concepts of algebraic specification and program development. Formal Aspects of Computing, 9(3):229–269, 1997. C. Strachey. Fundamental Concepts in Programming Languages. Higher Order and Symbolic Computation, 13, April 2000. Reprinted from Lecture notes, International Summer School in Computer Programming at Copenhagen, 1967. M. van den Brand et al. The asf+sdf meta-environment: a component-based language development environment. In Compiler Construction, LNCS. Springer-Verlag, 2001. F.W. v. Henke, H. Pfeifer, and H. Rueß. Guided tour through a mechanized semantics of simple imperative programming constructs. Ulmer Informatik-Berichte 96-11, Universit¨at Ulm, Fakult¨at f¨ur Informatik, 1997. Philip Wadler. The Essence of Functional Programming. In Proceedings of the 19th Symposium on Principles of Programming Languages, pages 1–14, Albuquerque, New Mexico, January 19 – 22, 1992. ACM Press. P. Wadler. A formal semantics of patterns in xslt. In Markup Technologies, Philadelphia, December 1999. Keith Wansbrough. A modular monadic action semantics. Master’s thesis, Department of Computer Science, University of Auckland, February 1997. David A. Watt. Why don’t programming language designers use formal methods? In Seminario Integrado de Software e Hardware - SEMISH’96, pages 1–6, Recife, Brazil, 1996. University of Pernambuco. Glynn Winskel. The Formal Semantics of Programming Languages: An Introduction. Foundation of Computing Series. MIT Press, Cambridge, MA, 1993.