This assignment uses the functional aspects of Scheme to construct a relatively simple but non-trivial program. Once you have finished this assignment you will have a good command of the functional style.
I will collect two files from your hw3 directory:
This is not an assignment that you can just jump into and do. There is some serious thinking involved. Please read everything carefully before you do anything else. It will save you a lot of time, and you will understand this whole area much better.
You may know that Google has a wonderful facility for converting units. For instance, suppose you are cooking and you forget how many teaspoons are in a cup. You can simple type "1 cup in teaspoons" into the Google search window, and the answer will come up. And you can even perform more complicated (or silly, depending on how you look at it) queries. For instance, if you haven't already done this—it's a pretty well-known way to amaze your friends—try typing
furlongs per fortnight in miles per hourinto the Google search box. And you can also type things like
27.5 furlongs per fortnight in miles per hourIn this assignment you are going to write a program that does more or less the same thing.
Before we get going, I should say that there are a number of such programs available on the web. I don't believe that any of them are written in Scheme. And further, I don't believe that looking at them is going to help you. (I know this because when I started to do this, that's exactly what I did, and I finally realized that it was getting me nowhere.) On the other hand, I am going to give you some fairly significant suggestions, which I hope will help you a lot in doing this. (And as usual, the work you hand in must be entirely your own in any case.)
(27.5 (furlong 1)(fortnight -1))
and the format of the "to" quantity will be
((mi 1)(hr -1))
and the actual query will be written as a Scheme function taking these two
arguments, like this:
(convert '(27.5 (furlong 1)(fortnight -1)) '((mi 1)(hr -1)))
Question: why are the quotes necessary?
(0.01023065476190476 (mi 1)(hr -1))
A user-friendly procedure would put this back into everyday English. But
we're not going to worry about that. We'll just leave it as is.One thing you will notice is that it is very simple indeed. All units are defined in terms of certain "elementary" units. All length units, for instance are represented in terms of meters. Now it would be actually more convenient in constructing this file to allow it to contain entries in which some units were defined in terms of other derived units, like this:
(yd (3 (ft 1)))
But this would complicate our processing, and so we won't do it.
We have not discussed how to read in a file to a Scheme program. We will deal with this later in the term. But in the meantime, here's how you do it: At the end of your file convert.scm put the following code:
------------------------
;; read-file produces a list whose elements are the expressions in the file.
(define (read-file)
(let ((expr (read)))
(if (eof-object? expr)
'()
(cons expr (read-file)))))
;; Here we go: read in the database.
(define source (with-input-from-file "units.dat" read-file))
------------------------
This will read in the file units.dat and put the contents as a Scheme
list into the variable source. Then you can just use that list as a
lookup table. (How do you look up things in that table? Think about
assoc.)
(m 2)
represents "meters squared", or "square meters"—these mean the same
thing.
((mi 1)(hr -1))
represents "miles per hour". And
((kg 1)(m 1)(sec -2))
represents "kilogram meters per second squared".
(1 (kg 1)(m 1)(sec -2))
represents "1 kilogram meter per second squared", which is also known as "1
newton", and is the unit of force in the MKS ("meter-kilogram-second")
system that I hope you are familiar with from whatever physics course you
took.
(convert quantity unit-list)
I've already shown you what the output looks like (i.e., the kind of Scheme
expression this expression evaluates to) above.Now here are some ideas that you may find useful. Of course you can really do this any way you want, and I certainly don't insist that you take any of these ideas exactly as I am writing them. (In the past I have found that students often have very original and interesting ideas about how to solve particular problems, and so I encourage you to experiment with different ways of doing things.)
First of all, let's look at the general idea of what you want to do. Conceptually, it's really pretty simple. You are given a quantity—say it is of the form
a Uwhere a is a number and U is a unit. (Of course U is really what we have called above a unit-list. For instance, U might be ((mi 1)(hr -2)). And suppose you want to convert this to a quantity whose units are V.
The first thing you have to check is that U is compatible with V. What this really means is that if you express both U and V in terms of the same elementary units, then U turns out to be a constant multiple of V. So the first thing you need to do is to transform U and V so they are expressed in terms of the same elementary units. You will need a procedure that does this. Applying the procedure, you will then get
U = u U-normalized V = v V-normalizedwhere U-normalized and V-normalized are written in elementary units, and where u and v are numbers.
Now U and V are compatible if and only iff U-normalized is the same as V-normalized. So you will need a procedure to check this.
Assuming then that U and V are compatible, you should be able to see that
U = (u/v) VIn fact, I want to you show this in your notes.txt discussion.
Finally then, we get
a U = (au/v) VSo that's what you have to do.
You will need a number of helper functions to do this. Give them useful names that will help you (and me and the grader and anyone else who needs to read your code) understand what you are doing. And comment them so it is clear what sorts of things they take as input and what they produce when evaluated. Usually a good example is the way to go when doing this.
You will also probably need a large number of temporary variables. Don't be afraid of the let special form. And you may find yourself nesting them, like this:
(let ((x ...)
(y ...))
(let ((z <something involving x>))
...
and this nesting might even be rather deep. Don't be afraid of this either.
But do give your temporary variables descriptive names like
first-unit or converted-ratio (not names like x or
y, as I did above). This is important. The grader and I will be
looking for this.
Be sure to test your procedure. Make up some miserably hard tests. Remember that a quantity could look like this:
(5 (joule 2)(N -3)(hr 1))(Although this makes little sense physically, your procedure should handle it just fine.)
To handle complex quantities like this, you will need to find a way of simplifying them. This is part of the process—described above—of representing quantities in terms of elementary units. Be careful about this. It's possible to get confused here. Write down carefully how you are approaching this problem.
Finally, I am going to take your notes.txt file very seriously. I want to know what problems you encountered as you did this assignment. What was easy? What took some thinking? What mistakes did you make, and how did you fix them? Would you do things differently a second time? And if you had more time, how would you extend this code to make it more useful and/or user-friendly? (I'm not looking for vague ideas here. If you have an idea, let me know how you would implement it. Don't write actual code, though.)
If you leave writing your notes.txt file until you have finished your coding, you most likely won't do a very good job. For one thing, you will have forgotten a lot. For another thing, you will probably be jammed for time. A better idea is to keep some sort of "diary" as you write your code. Then you can turn your diary into the essay in notes.txt
However: I don't want your notes.txt to be a bunch of raw unedited notes. I want it to be thoughtful and easy to read. I also don't want to see big long listings of test runs. It's a good idea to indicate how you tested your code. But don't give me all the gory details.
Remember that notes.txt should be written as if it were going to be read by someone who doesn't really know very much and is just learning about this, and perhaps is taking over your code. Don't assume that person is an expert.