-
Notifications
You must be signed in to change notification settings - Fork 2
/
lab12_part4.ml
152 lines (126 loc) · 4.79 KB
/
lab12_part4.ml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
(*
CS51 Lab 12
Imperative Programming and References
*)
(*
SOLUTION
*)
(*
Objective:
This lab provides practice with reference types and their use in
building mutable data structures and in imperative programming more
generally. It also gives further practice in using modules to abstract
data types.
There are 4 total parts to this lab. Please refer to the following
files to complete all exercises:
lab12_part1.ml -- Part 1: Implementing modules
lab12_part2.ml -- Part 2: Files as modules
lab12_part3.ml -- Part 3: Interfaces as abstraction barriers
-> lab12_part4.ml -- Part 4: Polymorphic abstract types (this file)
*)
(*====================================================================
Part 4: Adding serialization to imperative queues
In the textbook, we defined a polymorphic imperative queue signature
as follows:
module type IMP_QUEUE =
sig
type 'a queue
val empty : unit -> 'a queue
val enq : 'a -> 'a queue -> unit
val deq : 'a queue -> 'a option
end
In this part, you'll add functionality to imperative queues for
serializing of the queue. (You added serialization to a pure
(immutable) stack module in Lab 8.) The signature needs to be
augmented first; we've done that for you here: *)
module type IMP_QUEUE =
sig
type elt
type queue
val empty : unit -> queue
val enq : elt -> queue -> unit
val deq : queue -> elt option
val to_string : queue -> string
end ;;
(* Notice that we've changed the module slightly so that the element
type (`elt`) is made explicit.
The `to_string` function needs to know how to convert the individual
elements to strings. That information is best communicated by
packaging up the element type and its own `to_string` function in a
module that can serve as the argument to a functor for making
`IMP_QUEUE`s. (You'll recall this technique from Lab 8.)
Given the ability to convert the elements to strings, the `to_string`
function should work by converting each element to a string in order
separated by arrows " -> " and with a final end marker to mark the end
of the queue "||". For instance, the queue generated by enqueing, in
order, integer elements 1, 2, and 3 would serialize to the string
"1 -> 2 -> 3 -> ||" (as shown in the example below).
We've provided a functor for making imperative queues, which works
almost identically to the final implementation from the book (based on
mutable lists) except for abstracting out the element type in the
functor argument.
......................................................................
Exercise 9: Your job is to complete the implementation by finishing
the `to_string` function. (Read on below for an example of the use of
the functor.)
....................................................................*)
module MakeImpQueue (Elt : sig
type t
val to_string : t -> string
end)
: (IMP_QUEUE with type elt = Elt.t) =
struct
type elt = Elt.t
type mlist = mlist_internal ref
and mlist_internal =
| Nil | Cons of elt * mlist
type queue = {front: mlist; rear: mlist}
let empty () = {front = ref Nil; rear = ref Nil}
let enq x q =
match !(q.rear) with
| Cons (_hd, tl) -> assert (!tl = Nil);
tl := Cons(x, ref Nil);
q.rear := !tl
| Nil -> assert (!(q.front) = Nil);
q.front := Cons(x, ref Nil);
q.rear := !(q.front)
let deq q =
match !(q.front) with
| Cons (hd, tl) ->
q.front := !tl ;
(match !tl with
| Nil -> q.rear := Nil
| Cons(_, _) -> ());
Some hd
| Nil -> None
(* our solution for defining `to_string`: *)
let to_string {front; _} =
let rec to_string' mlst =
match !mlst with
| Nil -> "||"
| Cons (hd, tl) ->
Printf.sprintf "%s -> %s" (Elt.to_string hd) (to_string' tl) in
to_string' front
(* end of our solution *)
end ;;
(* To build an imperative queue, we apply the functor to an
appropriate argument. For instance, we can make an integer queue
module: *)
module IntQueue = MakeImpQueue (struct
type t = int
let to_string = string_of_int
end) ;;
(* And now we can test it by enqueueing some elements and converting
the queue to a string to make sure that the right elements are in
there. *)
let test () =
let open IntQueue in
let q = empty () in
enq 1 q;
enq 2 q;
enq 3 q;
to_string q ;;
(* Running the `test` function should have the following behavior:
# test () ;;
- : string = "1 -> 2 -> 3 -> ||"
*)