-
Notifications
You must be signed in to change notification settings - Fork 6
/
lab7_part2.ml
180 lines (144 loc) · 7.04 KB
/
lab7_part2.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
170
171
172
173
174
175
176
177
178
179
180
(*
CS51 Lab 7
Modules and Abstract Data Types
*)
(*
SOLUTION
*)
(* Objective: This lab practices concepts of modules, including files
as modules, signatures, and polymorphic abstract data types.
There are 4 total parts to this lab. Please refer to the following
files to complete all exercises:
lab7_part1.ml -- Part 1: Implementing modules
-> lab7_part2.ml -- Part 2: Files as modules (this file)
lab7_part3.ml -- Part 3: Interfaces as abstraction barriers
lab7_part4.ml -- Part 4: Polymorphic abstract types
*)
(*======================================================================
Part 2: Files as modules
A useful feature of OCaml is that it *automatically* wraps functions
and values that are defined in a single file into a module named after
that file. The module name is the name of the file with the first
letter capitalized. This functionality is in addition to the manual
definition of modules as you've just used in Part 1, and it is a
convenient way of separating code into separate namespaces when
writing a large program.
There are other source files included in this lab, other than the
`lab7_partn.ml` files. The file `color.ml` contains an implementation
of a system for managing colors. (Recall from lab 5 the idea of colors
as consisting of values for three color channels -- red, green, and
blue.) Take a look at it to see what functions and values it contains,
including a type for colors, and a means for converting values for the
three color channels into the abstract color type, extracting the
channels individually from colors, and converting some standard color
names to this color representation.
With the exception of Exercise 2A, you will need to modify *only*
the file `color.ml` to complete the exercises below.
A digression on accessing other modules:
You'll want to test this part of the lab using `ocamlbuild`, for
instance, with
% ocamlbuild -use-ocamlfind lab7_part2.byte
% ./lab7_part2.byte
The ocamlbuild command should automatically find modules that
you've written that reside in the same directory as your source,
compile those additional files, and link them to your compiled
program. You can then access functions from those files under the
module name, which (again) is the name of the file with the first
letter capitalized. For instance, if `color.ml` is in the same
directory as `lab7_part2.ml` (which it probably is), `ocamlbuild`
will find it and use it, since it is referenced through
expressions like `Color.red` and the like.
On the other hand, if you're testing with a top-level REPL, like
utop or ocaml, it will *not* automatically find those modules and
evaluate them for you. However, you can do so manually yourself
with the `#mod_use` directive, like this:
# #mod_use "color.ml" ;;
allowing you to then refer to elements of the module as, for
instance,
# Color.color_named ;;
- : Color.color_name -> int = <fun>
(Note the capitalized module name.)
Keep in mind however that the `#mod_use` and `#use` directives
look only at their argument files, not at any corresponding `.mli`
files, so the modules being used will *not* be restricted to the
signatures in the corresponding `.mli` files. For that, you'll
have to use the compiler (with `ocamlbuild` for instance).
........................................................................
Exercise 2A: Replace the `0` in the expression below with an
expression that extracts the red channel of the color named `Red`,
thereby naming the result `red_channel`. The expression will be
constructed from values in the `Color` module.
......................................................................*)
let red_channel : int =
let open Color in
red (color_named Red) ;;
(* Without the local open, we get the slightly more verbose
let red_channel : int =
let open Color in
Color.red (Color.color_named Color.Red) ;;
*)
(* Let's investigate one way that a signature can be useful. Although
`color.ml` contains an implementation of a basic color module, the
implementation is unintuitive and obscure -- truly *horrid* in
fact. (We did that on purpose.) You will want to change the
implementation of `color.ml`, rewriting it wholesale. At the same time,
you'll want to guarantee to users of the module (like this file
itself!) that the functionality stays the same; the way to do this is
through module signatures.
......................................................................
Exercise 2B: Add a file `color.mli`, in which you define an appropriate
signature for the `Color` module. Consider which types and values you
want revealed to the user and which you would prefer to be hidden.
Once you have `color.mli` implemented, you should still be able to
compile `color.ml` and run `color.byte`.
......................................................................*)
(* The module type for the `Color` module captures the interface we want
our color modules to obey. Your `color.mli` file should have the
following types and values declared. If you have trouble getting this
to work, you can find our solution in `color.mli`.
type color ;;
type color_name =
| Red
| Green
| Blue
| Orange
| Yellow
| Indigo
| Violet ;;
val to_color : int -> int -> int -> color ;;
val red : color -> int ;;
val green: color -> int ;;
val blue: color -> int ;;
val color_named: color_name -> color ;;
*)
(*......................................................................
Exercise 2C:
In the file `color.ml`, modify the implementation of a color module as
you see fit. Make the design choices that you think would be best for
a color module implementation. In particular, you should be able to
come up with a solution that is *much* clearer and more transparent
than the one currently in `color.ml`.
To pass the unit tests, you'll want the RGB values for the colors in
the `color_name` type to have the following values:
R | G | B | Color
----|-----|-----|--------
255 | 0 | 0 | Red
0 | 255 | 0 | Green
0 | 0 | 255 | Blue
255 | 165 | 0 | Orange
255 | 255 | 0 | Yellow
75 | 0 | 130 | Indigo
240 | 130 | 240 | Violet
......................................................................*)
(* See the solution in `color.ml` *)
(* Here's the payoff: A user who uses the `color` module, by virtue of
having to stay within the `color.mli` interface, will not notice *any
difference at all* between the two implementations of `color.ml`, the
horrid one we provided and the elegant one you've developed. The
underlying implementation can be changed any time in any way, so long
as the functionality provided stays consistent with the signature.
Correspondingly, consider the `List` module that you are familiar with
by now. You never needed to worry about how the `List` module was
implemented in order to use it; you only needed to understand the
interface.
Compartmentalization ftw! *)