The key to organizing the code is to understand that this:
datatype temp_dimension = Celsius | Fahrenheit
datatype dist_dimension = Meters | Centimeters | Kilometers
datatype units = Temp of temp_dimension | Dist of dist_dimension
Strongly suggests that the code should dispatch on type.
Add temp_dimension's
Since temp_dimension
only has two subtypes, its structure is a bit simpler. On the other hand, converting between the subtypes is more complicated than for dist_dimension
and warrants some testing. That's what makes the world interesting. To add temp_dimensions
the code has to handle four cases:
fun add_temp ((v1 : real, t1 : temp_dimension),
(v2 : real, t2 : temp_dimension)) =
let
val freeze = 32.0
val temp_ratio = (5.0 / 9.0)
fun cel2fahr (c : real) = (c / temp_ratio) + freeze
fun fahr2cel (f : real) = (f - freeze) * temp_ratio
in
case t1 of
Celsius => (
case t2 of
Celsius => v1 + v2
| Fahrenheit => v1 + fahr2cel(v2)
)
| Fahrenheit => (
case t2 of
Fahrenheit => v1 + v2
| Celsius => v1 + cel2fahr(v2)
)
end
Add dist_dimension's
While there are nine cases for an operation on dist_dimension
s:
fun add_dist ((v1 : real, t1 : dist_dimension),
(v2 : real, t2 : dist_dimension)) =
let
fun cent2meter (c : real) = c * 100.0
fun cent2kilometer (c : real) = c * 100000.0
fun meter2kilometer (m : real) = m * 1000.0
fun meter2cent (m : real) = m / 100.0
fun kilometer2cent (k : real) = k / 100000.0
fun kilometer2meter (k : real) = k / 1000.0
in
case t1 of
Centimeters => (
case t2 of
Centimeters => v1 + v2
| Meters => v1 + meter2cent v2
| Kilometers => v1 + kilometer2cent v2
)
| Meters => (
case t2 of
Centimeters => v1 + cent2meter v2
| Meters => v1 + v2
| Kilometers => v1 + kilometer2meter v2
)
| Kilometers => (
case t2 of
Centimeters => v1 + cent2kilometer v2
| Meters => v1 + meter2kilometer v2
| Kilometers => v1 + v2
)
end
Add
The add
function dispatches on the type units
as Ionut G. Stan suggested:
fun add ((v1, Temp t1), (v2, Temp t2)) = add_temp((v1, t1),(v2, t2))
| add ((v1, Dist t1), (v2, Dist t2)) = add_dist ((v1, t1),(v2, t2))
| add _ = raise Fail "Incompatible units cannot be added"
Comments
I find it easier to think clearly when I am explicit about types in function declarations. For example, it is easy to get tripped up on integer
versus real
in a language with strong static typing like SML.
I also don't worry about being brief. Correct is easier than correct + code golf.
Finally, there are a number of higher level functions that could be written to handle the repetitive parts of this sort of program. It's fun to think about them, but they don't make for the clearest of answers.