However, if you do implement these functions, you will have a chance to practice mutual recursion, and we will grade each of the helper functions described below for partial credit, in case the complete unit calculator application does not fully work.
Some examples:
> (simplify '(3 ("kg" "meter" "second" "second") ("kg" "kg" "second")))
(3 ("meter" "second") ("kg"))
> (simplify '(3.14 ("meter" "second") ("meter" "second" "second")))
(3.14 () ("second"))
Some example runs include
> (multiply '(2.0 ("kg") ("second")) '(3.0 ("meter") ("second")))
(6.0 ("kg" "meter") ("second" "second"))
> (divide '(12.5 ("meter") ("second")) '(0.5 ("meter") ()))
(25.0 () ("second"))
(define mini-db
'(
("foot" (12. ("inch") ()))
("mile" (5280. ("foot") ()))
("inch" (0.0253995 ("meter") ()))
("hour" (60. ("minute") ()))
("minute" (60. ("second") ()))
("mph" (1. ("mile") ("hour")))
("coulomb" (1. ("ampere" "second") ()))
("joule" (1. ("kg" "meter" "meter") ("second" "second")))
("ohm" (1. ("volt") ("ampere")))
("volt" (1. ("joule") ("coulomb")))
("watt" (1. ("joule") ("second")))
))
This association list, named mini-db,
as well as a much larger one, named db, are both available from the
top-level assignments page in files named
unicalc-db.scm and unicalc-mini-db.scm.
(assoc "foot" mini-db) ==> ("foot" (12. ("inch") ()))
(assoc "meter" mini-db) ==> #f
This assoc function will do almost all of the work of the next Unicalc piece, conv-unit.
If this were the entire database under consideration, note that "ampere", "meter", and "second" are all basic quantities, since they are not the LHS of any definition. Indeed, these three units are, in fact, basic units in the international system (SI)
Write a Scheme function (conv-unit unitname DB) that takes a string representing a unit and the database association list as arguments. If the unit is defined in the database, it returns that unit's equivalent quantity list, otherwise it returns a standard quantity list representing the unit: (1.0 ("unit") ()). For example, if mini-db is the database consisting of the list of the pairs above, then:
> (conv-unit "hour" mini-db)
(60.0 ("minute") () )
> (conv-unit "meter" mini-db)
(1.0 ("meter") () )
Thus, conv-unit "recognizes" basic, irreduceable units because they are the
ones that can only be defined in terms of themselves, as in the latter "meter"
example. Here, you'll want to use assoc to do most of the work.
When the numerator and denominator lists within a quantity list contain only basic units, we'll say that that quantity list is normalized. Again, basic units are those which do not appear on the left-hand side of any entry in the unit database. That is, they are irreducible.
In order to convert one unit to another, we must express those units in a common form. This common form will be simplified, normalized quantity lists. To this end, write a function (norm-unit s DB) that returns a normalized quantity list that represents the unit whose name is s. Again, a normalized quantity list is one in which the numerator and denominator lists contain only basic units. Here, s is the string name of a unit and DB is a database of units, as above.
Here's how norm-unit will work: in the case that s is a basic unit, norm-unit will return the same thing that conv-unit does: (1.0 (s) ()). In the case that s is not a basic unit, you should use conv-unit to "look up" s's definition in the database. Then, you will need to normalize that resulting quantity list -- probably by calling norm, below.
Here are some example runs of norm-unit. There are two
unicalc database files available from the top-level assignment page:
one is called unicalc-mini-db.scm (which defines mini-db)
and a much larger one is called unicalc-db.scm (which defines db). To use them,
download them to the same directory as your homework file, and simply put the
following two lines at the top:
(load "unicalc-db.scm") ;; defines mini-db
(load "unicalc-mini-db.scm") ;; defines db
> (norm-unit "second" mini-db)
(1.0 ("second") ())
> (norm-unit "hour" db)
(3600.0 ("second") ())
> (norm-unit "ohm" db)
(1.0 ("kg" "meter" "meter") ("ampere" "ampere" "second" "second" "second"))
Hint: read the next part before solving this one!
Hint: notice that norm does the same thing as norm-unit, only with a slightly different first input. Because of this, norm and norm-unit are most concisely written as mutually recursive functions, i.e., each calls the other at one or more points. The key to writing mutually recursive functions is being sure you have a clear sense of what each function does at an abstract level -- that way, you can use either as appropriate.
How to think about mutual recursion? Well, for norm, you might consider the following three cases:
Some examples of norm in action:
> (norm '(20.0 ("hour") ()) db)
(72000.0 ("second") ())
> (norm '(15.0 ("ohm") ("volt")) db)
(15.0 () ("ampere"))
(define (convert QL1 QL2 DB) (divide (norm QL1 DB) (norm QL2 DB)))Try it out with the original examples...
> (convert '(1 ("mile") ("hour")) '(1 ("meter") ("second")) db)
(0.447032 () ())
This result is indicating that 1 mile-per-hour == 0.447032 * 1 meter-per-second
Precision of your answers...
Do not worry if your answer is slightly off from the above - for example, less
than a few hundredths. For example, you might have an output of
When evaluating your Unicalc, we will not worry about small differences
that can arise due to different
levels of precision being used (or different orders of the operations being
performed).
(0.447031923888 () ())
Here is an example with some units left over, because it's not possible to
convert miles-per-hour to feet:
where this time the result is showing that 1 mile-per-hour == 1.46666 feet-per-second. Since there
were no time units in the denominator and there should have been, the result shows the basic (irreduceable) time units there.
In both of these examples, only the first few significant figures have been shown.
> (convert '(1 ("mile") ("hour")) '(1 ("foot") ()) db)
(1.466666 () ("second"))
If you made it to here, congratulations, you have completed Unicalc!