-
Notifications
You must be signed in to change notification settings - Fork 2
/
lab12_part3.ml
170 lines (135 loc) · 5.09 KB
/
lab12_part3.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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
(*
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 (this file)
lab12_part4.ml -- Part 4: Polymorphic abstract types
*)
(*====================================================================
Part 3: Appending mutable lists
Recall the definition of the mutable list type from Section 15.4 of
the textbook: *)
type 'a mlist = 'a mlist_internal ref
and 'a mlist_internal =
| Nil
| Cons of 'a * 'a mlist ;;
(* Mutable lists are just like regular lists, except that each Nil or
cons is a *reference* to a mutable list, so that it can be updated. *)
(*....................................................................
Exercise 5: Define a polymorphic function `mlist_of_list` that
converts a regular list to a mutable list, with behavior like this:
# let xs = mlist_of_list ["a"; "b"; "c"] ;;
val xs : string mlist =
{contents = Cons ("a",
{contents = Cons ("b",
{contents = Cons ("c",
{contents = Nil})})})}
# let ys = mlist_of_list [1; 2; 3] ;;
val ys : int mlist =
{contents = Cons (1,
{contents = Cons (2,
{contents = Cons (3,
{contents = Nil})})})}
....................................................................*)
let rec mlist_of_list (lst : 'a list) : 'a mlist =
match lst with
| [] -> ref Nil
| hd :: tl -> ref (Cons (hd, mlist_of_list tl)) ;;
(*....................................................................
Exercise 6: Define a function `mlength` to compute the length of an
`mlist`. Try to do this without looking at the solution that is given
in the book. (Don't worry about cycles...yet.)
# mlength (ref Nil) ;;
- : int = 0
# mlength (mlist_of_list [1; 2; 3; 4]) ;;
- : int = 4
....................................................................*)
let rec mlength (mlst : 'a mlist) : int =
match !mlst with
| Nil -> 0
| Cons (_hd, tl) -> 1 + mlength tl ;;
(*....................................................................
Exercise 7: What is the time complexity of the `mlength` function in
terms of the length of its list argument? Provide the tightest
complexity class, recorded using the technique from lab 10.
....................................................................*)
type complexity =
| Unanswered
| Constant
| Logarithmic
| Linear
| LogLinear
| Quadratic
| Cubic
| Exponential ;;
(* ANSWER: The `length` function is linear in the length of its
argument: O(n). *)
let length_complexity : complexity = Linear
(*....................................................................
Exercise 8: Now, define a function `mappend` that takes a first
mutable list and a second mutable list and, as a side effect, causes
the first to *become* (as a side effect) the appending of the two
lists. A question to think about before you get started:
What is an appropriate return type for the `mappend` function?
(You can glean our intended answer from the examples below, but
try to think it through yourself first.)
Examples of use:
# let m1 = mlist_of_list [1; 2; 3] ;;
val m1 : int mlist =
{contents = Cons (1,
{contents = Cons (2,
{contents = Cons (3,
{contents = Nil})})})}
# let m2 = mlist_of_list [4; 5; 6] ;;
val m2 : int mlist =
{contents = Cons (4,
{contents = Cons (5,
{contents = Cons (6,
{contents = Nil})})})}
# mlength m1 ;;
- : int = 3
# mappend m1 m2 ;;
- : unit = ()
# mlength m1 ;;
- : int = 6
# m1 ;;
- : int mlist =
{contents = Cons (1,
{contents = Cons (2,
{contents = Cons (3,
{contents = Cons (4,
{contents = Cons (5,
{contents = Cons (6,
{contents = Nil})})})})})})}
....................................................................*)
(* Answer to thought question:
What is an appropriate return type for the `mappend` function?
(You can glean our intended answer from the examples below, but
try to think it through yourself first.)
ANSWER: Since `mappend` is called for its side effect, `unit` is
the appropriate return type.
*)
let rec mappend (xs : 'a mlist) (ys : 'a mlist) : unit =
match !xs with
| Nil -> xs := !ys
| Cons (_hd, tl) -> mappend tl ys ;;
(* What happens when you evaluate the following expressions
sequentially in order?
# let m = mlist_of_list [1; 2; 3] ;;
# mappend m m ;;
# m ;;
# mlength m ;;
Do you understand what's going on? *)