Tute - Completed (Week 10)
Table of Contents
1. Overloading
Consider the following file in Haskell:
class Compare a where cmp :: a -> a -> Bool instance Compare Int where cmp x y = x <= y instance (Compare a) => Compare [a] where cmp xs ys = and (zipWith cmp xs ys) group :: (Compare a) => [a] -> [[a]] group [] = [] group (x:xs) = let (ys, zs) = span (cmp x) xs in (x:ys) : group zs
How would the type checker translate this code to remove the type classes?
type CompareDict a = (a -> a -> Bool) intCompareDict :: CompareDict Int intCompareDict x y = x <= y listCompareDict :: CompareDict a -> CompareDict [a] listCompareDict cmp xs ys = and (zipWith cmp xs ys) group :: CompareDict a -> [a] -> [[a]] group cmp [] = [] group cmp (x:xs) = let (ys, zs) = span (cmp x) xs in (x:ys) : group cmp zs
2. Subtyping
2.1. Coercions and Subtyping
You are given the type Rectangle
, parameterised by its height and width, and the type Square
parameterised by the length of one of its sides. Neither type is mutable.
a. Which type is the subtype, which type is the supertype?
b. Give a subtype/supertype ordering of the following set of function types: Rectangle -> Rectangle
, Rectangle -> Square
, Square -> Rectangle
, Square -> Square
.
c. Define a data type Square
and a data type Rectangle
in Haskell. Then define a coercion function from elements of the subclass to elements of the superclass.
d. Show that the ordering you have given in the previous question is correct by defining coercion functions for each pair of types in a subtyping relationship in part (b).
a. Square
is a subtype of Rectangle
.
b. Rectangle -> Square
is a subtype of Rectangle -> Rectangle
and Square -> Square
, which are both subtypes of Square -> Rectangle
.
c.
data Square = Square Int data Rectangle = Rect Int Int coerce :: Square -> Rectangle coerce (Square w) = Rect w w
d.
rs2rr :: (Rectangle -> Square) -> (Rectangle -> Rectangle) rs2rr f = coerce . f rs2ss :: (Rectangle -> Square) -> (Square -> Square) rs2ss f = f . coerce rr2sr :: (Rectangle -> Rectangle) -> (Square -> Rectangle) rr2sr f = f . coerce ss2sr :: (Square -> Square) -> (Square -> Rectangle) ss2sr f = coerce . f
2.2. Constructor variance
List some examples of a covariant, contravariant and invariant type constructor.
A function's argument is contravariant. A function's return type is covariant. A data type like Endo
below
is invariant:
data Endo a = E (a -> a)
3. LCR Conditions
Consider the following two processes, each manipulating a shared variable \(x\), which starts out at \(0\).
Thread 1: \(x := x + 1; x:= x - 1;\)
Thread 2: \(x := x \times 2\)
a) What are the possible final values of \(x\) assuming each statement is executed atomically?
\(x\) is either zero or one.
b) Rewrite the above program into one where each statement obeys the limited critical reference restriction. What are the possible final values now?
Thread 1: \(\textbf{var}\ t; t := x; x := t + 1; t := x; x := t - 1;\)
Thread 2: \(\textbf{var}\ u; u := x; x := u \times 2\)
This allows the final write of thread 2 to happen well after the read of thread 2. Thus, a final result of \(x = 2\) or \(x = -1\) is now also possible.
c) How could locks be used in your answer to (b) to ensure that only the final results from part (a) are possible?
Each formerly-atomic statement must now be protected by a shared lock \(\ell\):
Thread 1: \(\begin{array}{l}\textbf{var}\ t; \mathbf{take}(\ell); t := x; x := t + 1; \mathbf{release}(\ell);\\ \mathbf{take}(\ell); t := x; x := t - 1; \mathbf{release}(\ell);\end{array}\)
Thread 2: \(\textbf{var}\ u; \mathbf{take}(\ell); u := x; x := u \times 2; \mathbf{release}(\ell);\)
4. Session Types
Using the session-typed process calculus introduced in the lecture, specify the process and type for a server that first takes the name of a product for sale, then provides a choice between two services:
Quote
: output a price of typePrice
, then do nothing.Sell
: input payment details of typeCardNo
, output a receipt of typeReceipt
, then do nothing.
a) Define the types (i) Quote
and (ii) Sell
in terms of Price
, CardNo
, Receipt
.
(i) Quote = Price ⨂ 1 (ii) Sell = CardNo ⅋ (Receipt ⨂ 1)
b) Assume we have processes give_price |- Γ,y:Price
that transmits a Price
along channel y
and complete_sale |- Γ,y:CardNo,z:Receipt
that accepts a
CardNo
on channel y
then sends a Receipt
on channel z
.
(You can leave their typing derivations unexpanded.)
Specify processes (i) quote
of type Quote
and (ii) sell
of type Sell
and show their typing derivations.
(i) quote := x[y].(give_price | x[].0) |- Γ,x:Quote -------------- 1 give_price |- Γ,y:Price x[].0 |- Δ,x:1 ------------------------------------------- ⨂ x[y].(give_price | x[].0) |- Γ,Δ,x:Price ⨂ 1 (ii) x(y).x[z].(complete_sale | x[].0) |- x:Sell -------------- 1 complete_sale |- Γ,y:CardNo,z:Receipt x[].0 |- Δ,x:1 ---------------------------------------------------------- ⨂ x[z].(complete_sale | x[].0) |- Γ,Δ,y:CardNo,x:Receipt ⨂ 1 ----------------------------------------------------------------- ⅋ x(y).x[z].(complete_sale | x[].0) |- Γ,Δ,x:CardNo ⅋ (Receipt ⨂ 1)
Note, it is not essential that the complete_sale
has type dual(CardNo)
rather than CardNo
just because it is the receiver.
It only matters that the processes on corresponding sides of the channel
have types that are dual to each other. So it's fine for complete_sale
to have type CardNo
for y
as long as the process on the other side
of y
has for it some type Blah = dual(CardNo)
.
c) Suppose the names of products for sale have type Name
.
Now specify the process and derive the type of the entire server.
(You can leave quote
, sell
and their typing derivations unexpanded.)
server := x(w).x.case(quote, sell)) = x(w).x.case((x[y].(give_price | x[].0)) | (x(y).x[z].(complete_sale | x[].0))) |- x:Name ⅋ (Quote & Sell) quote |- w:Name,x:Quote sell |- w:Name,x:Sell ------------------------------------------------- & x.case(quote, sell) |- w:Name,x:Quote & Sell --------------------------------------------------- ⅋ x(w).x.case(quote, sell) |- x:Name ⅋ (Quote & Sell)
d) (i) Specify a process and type of a client seeking a quote from this server. You can specify the types as duals of the types above.
(ii) Show the typing derivation for the part that chooses to get the quote.
You can leave get_price
and its typing derivation unexpanded.
(i)
We must choose types that are dual to that of the server.
Suppose put_name
sends a dual(Name)
on channel w
and get_price
receives a dual(Price)
on channel y
.
Then the following process will be able to be composed with the server
according to the Cut rule.
First, let get_quote := x[inl].x(y).x().get_price
.
Then,
client := x[w].(put_name | get_quote) |- dual(Name) ⨂ (dual(Quote) ⨁ dual(Sell))
(ii) Note that dual(Quote) = dual(Price ⨂ 1) = dual(Price) ⅋ ⊥
.
The typing derivation for get_quote
is as follows:
get_price |- y:dual(Price) ---------------------------------- ⊥ x().get_price |- y:dual(Price),x:⊥ --------------------------------------- ⅋ x(y).x().get_price |- x:dual(Price) ⅋ ⊥ ----------------------------------------------------- ⨁ x[inl].x(y).x().get_price |- dual(Quote) ⨁ dual(Sell)
e) Show the reduction of the process formed by parallel composition of the above client process with the server process.
ch x. (client | server) == (unfolding client, server) ch x. ((x[w].(put_name | get_quote)) | x(w).x.case(quote, sell)) ==> (β⊗⅋) ch w. (put_name | ch x. (get_quote | x.case(quote, sell))) == (unfolding get_quote) ch w. (put_name | ch x. (x[inl].x(y).x().get_price | x.case(quote, sell))) ==> (β⊕&1) ch w. (put_name | ch x. (x(y).x().get_price | quote)) == (unfolding quote) ch w. (put_name | ch x. (x(y).x().get_price | x[y].(give_price | x[].0))) == (Swap) ch w. (put_name | ch x. (x[y].(give_price | x[].0) | x(y).x().get_price)) ==> (β⊗⅋) ch w. (put_name | ch y. (give_price | ch x. (x[].0 | x().get_price))) ==> (β1⊥) ch w. (put_name | ch y. (give_price | get_price)))