Denotational semantics for the calculator language

Denotational semantics for the calculator
language
Antti-Juhani Kaijanaho
January 16, 2017 (typo fixes on January 17, 2017)
Remember the calculator language?1 Here is its abstract syntax again:
E, F ∈
E, F ::=
|
|
|
|
|
|
|
c, d ∈
x, y, z ∈
Exp
E+F
E−F
E∗F
E/F
−E
c
x
let x = E in F
Const
Var
When making a denotational semantics for this language, we have to
decide what set contains all the possible results of the language. It is a bit
tricky, as the language is intended to be used with floating point numbers,
and FP numbers have many peculiar qualities. The most important one is the
existence of −∞, +∞, and NaN (not a number), which are valid floating point
numbers produced by various divisions by zero. Another issue is that floating
point numbers are not exact reals. I will not discuss how to define these
details mathematically, so I will simply assume that there is a set Double
of standard double-precision floats which possesses the usual floating-point
operations +, −, × and ·· , with division by zero being well defined (resulting
in either an infinity or a NaN). I will further assume that there is a mapping
V : Const → Double that gives each literal constant a floating-point value.
A calculator expression may contain variables that are not defined in
the expression itself; such variables are called free variables. An expression
1
https://tim.jyu.fi/view/128897
1
containing free variables has a value only if those variables have a defined
value when the value is being constructed. It is traditional to say that the
values of the free variables are given in the environment. Mathematically,
we may model the environment as a mapping from variable names to either
values or an undefined value. Conventionally, this undefined value is written
⊥ and called bottom; let us write that for all sets X for which ⊥ 6∈ X holds,
the set X⊥ means the set X ∪ {⊥}. Thus, the environment is some function
Var → Double⊥ .
For convenience, let us suppse that for every operation of Double there
is an operation of the same name for Double⊥ that results in ⊥ if any of its
operands is ⊥. Thus, for example, ⊥ + 2 = 2 + ⊥ = ⊥ + ⊥ = ⊥.
In denotational semantics, then, the meaning of a calculator program is
a mapping (Var → Double⊥ ) → Double⊥ : the idea is, that given floatingpoint values for any nonlocal variables, the program computes a particular
floating-point number. We can define then a function from programs to their
meanings using structural recursion and definition by cases:
E : Exp → (Var → Double⊥ ) → Double⊥
E JE + F K σ = E JEK σ + E JF K σ
E JE − F K σ = E JEK σ + E JF K σ
E JE ∗ F K σ = E JEK σ × E JF K σ
E JEK σ
E JE/F K σ =
E JF K σ
E J−EK σ = −E JF K σ
E JcK σ = V JcK σ
E JxK σ = σ JxK
(
E JF K (σ[x := E JEK σ]) if σ JxK = ⊥
E Jlet x = E in F K σ =
⊥
otherwise
(1)
(2)
(3)
(4)
(5)
(6)
(7)
(8)
There are a couple of notational points here that are unusual in general
mathematics but common in denotational semantics:
• Phrases in the language being defined (the object language) are written
inside the semantic brackets J K; terminal and nonterminal symbols of
the abstract syntax are used inside the semantic brackets as sort of
metasyntactic variables, standing for unknown phrases of the language.
• Function calls are written without parentheses, like in Haskell: E JEK σ
means the call of the function E with two arguments: JEK and σ.
2
• Where σ is a function from variable names to values, then σ[x := v] is
that same function except that x has the value v.
Notice how this denotational semantics is fairly easy to write as a Haskell
function
import qualified Data.Map.Strict as Map
...
eval : E -> Map.Map Var Double -> Maybe Double
I leave this as an exercise to the reader. However, the Interpreter visitor
of the Java implementation2 is also fairly close to the definition of E above
(only, due to the peculiarities of Java, Interpreter is not a function but a
visitor class).
2
https://yousource.it.jyu.fi/ohjelmointikielten-periaatteet/
laskin-2017/blobs/master/Interpreter.java
3