Some calculations with the denotational semantics of the simply-typed lambda calculus (the full type frame). Here's the one we started in class: Let H_4 = { p |-> o, q |-> o, + |-> o -> o -> o } and eta_4 = { p |-> 3, q |-> 4, + |-> primAdd }, where primAdd is the addition function on natural numbers (e.g., ((primAdd 3) 4) = 7). For later use, we make the following definition (from the lambda semantics). Let f_id(y) = [[H_3[x |-> o] |> x : o]](eta_5), where eta_5(x) = y and eta_5(z) = eta_4(x), if z =/= x Also for later use, let eta_53(x) = 3 and eta_53(z) = eta_4(z), if z =/= x We calculate as follows: [[H_3 |> ((+ ((\x:o.x) p)) q) : o]](eta_4) = { by definition of [[ ]] for applications } ([[H_3 |> (+ ((\x:o.x) p)) : o -> o]](eta_4)) ([[H_3 |> q : o]](eta_4)) = { working on the operand at right, by definition of [[ ]] for variables } ([[H_3 |> (+ ((\x:o.x) p)) : o -> o]](eta_4)) (eta_4(q)) = { by definition of eta_4 } ([[H_3 |> (+ ((\x:o.x) p)) : o -> o]](eta_4)) 4 = { by definition of [[ ]] for applications } (([[H_3 |> + : o -> (o -> o)]](eta_4)) ([[H_3 |> ((\x:o.x) p) : o]](eta_4))) 4 = { working on the operator, at left, by definition of [[ ]] for variables } ((eta_4(+)) ([[H_3 |> ((\x:o.x) p) : o]](eta_4))) 4 = { by definition of eta_4 } (primAdd ([[H_3 |> ((\x:o.x) p) : o]](eta_4))) 4 = { by definition of [[ ]] for applications } (primAdd (([[H_3 |> (\x:o.x) : o -> o]](eta_4)) ([[H_3 |> p : o]](eta_4)))) 4 = { working on the operand at right, by definition of [[ ]] for variables } (primAdd (([[H_3 |> (\x:o.x) : o -> o]](eta_4)) (eta_4(p)))) 4 = { by definition of eta_4 } (primAdd (([[H_3 |> (\x:o.x) : o -> o]](eta_4)) 3)) 4 = { by definition of [[ ]] for lambda abstractions, see f_id def above } (primAdd (f_id 3)) 4 = { by definition of f_id and eta_53 above } (primAdd ([[H_3[x |-> o] |> x : o]](eta_53))) 4 = { by definition of [[ ]] for variables } (primAdd (eta_53(x))) 4 = { by definition of eta_53 above } (primAdd 3) 4 = { by definition of primAdd (see above) } 7 Here's another example with functions. Let H_6 = { a |-> o, b |-> o, c |-> o } and eta_6 = { a |-> 5, b |-> 4, c |-> 1 }, For later use, we make the following definition (from the lambda semantics). Let f_k(z) = [[H_6[x |-> o] |> (\y:o.x): o->o]](eta_7), where eta_7(x) = z and eta_7(n) = eta_4(n), if n =/= x Also for later use, let eta_75(x) = 5 and eta_75(n) = eta_7(n), if n =/= x For later use, we also make the following definition. Let f_k_inner(z) = [[H_6[x |-> o][y |-> o] |> x: o]](eta_8), where eta_8(y) = z and eta_8(n) = eta_75(n), if n =/= y Also for later use, let eta_84(y) = 4 and eta_84(n) = eta_75(n), if n =/= y We calculate as follows: [[H_6 |> (((\x:o.(\y:o.x)) a) b) : o]](eta_6) = { by definition of [[ ]] for applications } ([[H_6 |> ((\x:o.(\y:o.x)) a) : o->o]](eta_6)) ([[H_6 |> b : o]](eta_6)) = { working on the right, by definition of [[ ]] for variables } ([[H_6 |> ((\x:o.(\y:o.x)) a) : o->o]](eta_6)) (eta_6(b)) = { by definition of eta_6 } ([[H_6 |> ((\x:o.(\y:o.x)) a) : o->o]](eta_6)) 4 = { by definition of [[ ]] for applications } (([[H_6 |> (\x:o.(\y:o.x)) : o->(o->o)]](eta_6)) ([[H_6 |> a : o]](eta_6))) 4 = { working on the right, by definition of [[ ]] for variables } (([[H_6 |> (\x:o.(\y:o.x)) : o->(o->o)]](eta_6)) (eta_6(a))) 4 = { by definition of eta_6 } (([[H_6 |> (\x:o.(\y:o.x)) : o->(o->o)]](eta_6)) 5) 4 = { by definition of [[ ]] for lambda abstractions } (f_k(5)) 4 = { by definition of f_k, note how the 5 is recorded in eta_75 (see above) } ([[H_6[x |-> o] |> (\y:o.x): o->o]](eta_75)) 4 = { by definition of [[ ]] for lambda abstractions } f_k_inner(4) = { by definition of f_k_inner, note how the 5 is recorded in eta_84 } [[H_6[x |-> o][y |-> o] |> x: o]](eta_84) = { by definition of [[ ]] for variables } eta_84(x) = { by definition of eta_84 } eta_75(x) = { by definition of eta_75 } 5