Harvey Mudd College

Computer Science 60

Spring 1998

Assignment 2

Due Thursday 12 February 1998

Unicalc Prototype

This assignment is 50 points of your overall grade.

The problems are stated with respect to the Unicalc program, which is described here. Later on the term, we'll devise a Java version of Unicalc, the information processing part of which is based upon the rex-language prototype in this assignment. This assignment exercises both high- and low-level functional programming concepts.

The program Unicalc converts quantities expressed in one type of unit to those of another. Here is an example of Unicalc in action on turing. The user types the bold-face characters.


    To convert from units of: miles
    to units of: feet
    Multiply by: 5280
    or
    Divide by: 0.000189394

    To convert from units of: miles / hour
    to units of: feet / second
    Multiply by: 1.46667
    or
    Divide by: 0.681818

    To convert from units of: kg / meter second^2
    to units of: pascal
    Multiply by: 1
    or
    Divide by: 1

Unicalc works on multiplicative conversions only, so it does not perform conversions such as Fahrenheit to Celsius. Unicalc can handle conversions involving multiplication, division, and raising to powers (the ^ symbol above).

Functional programming provides a natural way to implement the kernel of Unicalc. In this assignment, we walk through some of the data representations and issues, and ask you to write the functional code in rex.

To start with, Unicalc reads a conversion database, which is essentially a list of equations defining one type of unit in terms of another. For example, mile might be defined in terms of foot, foot in terms of inch, etc. This database does not contain conversions for everything to everything; that would tend to make it very large and maintenance would be error-prone. Instead, all conversions done by Unicalc are derived from basic ones in the database.

Not every unit is defined in terms of others in the database. Some are intentionally left undefined. We'll call the latter basic units.

When Unicalc is asked to convert a number of units A into a number of units B, both A and B are first converted into expressions involving only basic units, say A' and B' respectively. If A and B are interconvertible, only the numeric multipliers in A' and B' should be different. The result is then simply the ratio of the numeric multipliers for the "multiply by" part of the answer, or its reciprocal, for the "divide by" part.

The internal representation of a quantity in Unicalc is a list of three items:

For reasons to be described, the lists are sorted alphabetically. Each of the lists is considered to be a product of the units within. For example, the quantity we would write as 1 kg / meter second^2 would have the following representation:

[1.0, ["kg"], ["meter", "second", "second"]]

1. [10 points] The representation of a quantity is said to be simplified if there is no element common to its numerator and denominator lists. A way to convert a quantity to an equivalent simplified form is to simply remove any pairs of elements common to its numerator and denominator. Write a rex definition for a function simplify of one argument which yields a corresponding simplified representation. For example:


test(simplify([3, ["kg", "meter", "second", "second"], 
                  ["kg", "kg", "second"]], 
 
              [3, ["meter", "second"], ["kg"]]));

(Hint: Use a rule-based approach and exploit the fact that the lists are sorted.)

2. [10 points] Construct rex functions multiply and divide which respectively multiply and divide two quantities, yielding a simplified result. For example:

test(multiply([2.0, ["kg"], ["second"]], [3.0, ["meter"], ["second"]]),
 
              [6.0, ["kg", "meter"], ["second", "second"]]);

(Hint: Use the previous hint.)

3. [5 points] The Unicalc database is represented as an association list, a list of pairs, each pair consisting of a single unit and an expression for the unit. Essentially these pairs are defining equations for various units, and we can refer to the elements of the pairs as the LHS (left-hand side) and RHS (right-hand side) for this reason. For example, a sample database might be defined as:


db = 
[
  ["foot",   [12.,       ["inch"],   []]],
  ["mile",   [5280.,     ["foot"],   []]],
  ["inch",   [0.0253995, ["meter"],  []]],
  ["hour",   [60.,       ["minute"], []]],
  ["minute", [60.,       ["second"], []]],
  ["mph",    [1.,        ["mile"],   ["hour"]]]
];
which happens to be the current contents of /cs/cs60/a/unicalc_mini_db.rex (although this may change).

(The [ ] above represent the denominators of the RHS. These can be seen as the unit or multiplicative identity for the type of symbolic multiplication we use.)

If this were the entire database, we note that "meter" and "second" are both basic quantities, since they are not the LHS of any definition.

Write a rex function conv_unit which takes a string representing a unit and the database association list as arguments. If the unit is defined in the database, it returns the RHS, otherwise it returns a simplified expression representing the unit. For example, if db is the database consisting of the list of the pairs above, then:

test(conv_unit("hour", db), [60.0, ["minute"], [] ] );
 
test(conv_unit("meter", db), [1.0, ["meter"], [] ] );

4. [15 points] When the lists in such a representation contain only basic units, we'll say that the representation is normalized. In order to convert one quantity to a multiple of another, we must both normalize and simplify the quantities. Modify your function conv_unit to one called norm_unit which returns a normalized representation of the unit.

Here's how norm_unit would work: In the case that conv_unit finds the unit as an LHS in the database, we want to go through the numerator and denominator lists of the RHS, normalizing the units in the lists and multiplying (using your function multiply) the results together. Since your multiply function always yields a simplified result, you are guaranteed that the product of a list of units is simplified. Once you have normalized both the numerator and denominator, use your function divide to get the overall result of normalization, then combine with the multiplier. This process is recursive, as norm_unit calls itself when it normalizes the units in each of the RHS lists.

test(norm_unit("hour", db), [3600.0, ["second"], [] ] );

5. [5 points] Construct the function norm which normalizes any expression with respect to the database. Since an expression always has the form [M, N, D], and N and D consist of lists of units, you would apply norm_unit to each element in N and D, multiply the results together to get normalized quantities N1 and D1, then divide N1 by D1 and combine with the factor M. In fact, you already did something very much like this in part 4, so you may wish to go back and replace that code with a use of norm to make it more elegant. This introduces mutual recursion between norm and norm_unit, but that's ok.

test(norm([1, ["hour"], []], db), [3600.0, ["second"], [] ] );

6. [5 points] The conversion process from A to B can now be seen as simply

          divide(norm(A, DB), norm(B, DB))

If direct conversion is possible, the numerator and denominator of this result should both be empty lists. If there is a mismatch, the numerator or denominator will show the deficit multipliers. Provide a user interface function convert such that convert(A, B, DB) will perform the conversion and output the units if a direct conversion is possible, or which explains the deficit if not, in the form shown below. Examples:

 
    test(convert([1, ["mile"], []], [1, ["inch"], []], db),
         "multiply by 63360 or divide by 1.57828e-05");

    test(convert([1, ["mile"], ["second"]], [1, [], []], db),
         "There is a mismatch in units."); 
Here db is a variable bound to the database association list. For converting numbers to strings, you may wish to use the rex built-ins make_string which makes a string of its argument, and concat which concatenates an arbitrary number of string arguments.

In /cs/cs60/rex you will find:

unicalc_mini_db.rex

an equation for db defining the mini database above

unicalc_db.rex

an equation for db defining a more comprehensive database

Test cases will be found in:

 
     /cs/cs60/a/a2.rex