Tom Murphy amindfv at gmail.com
Sat Mar 19 04:02:19 CET 2011

```On Fri, Mar 18, 2011 at 5:02 AM, Magnus Therning <magnus at therning.org>
wrote:

> On Thu, Mar 17, 2011 at 21:17, Tom Murphy <amindfv at gmail.com> wrote:
> > Hi All,
> >      Is there a good way to easily convert between units?
> >      For example, let's say I have a data type:
> >      data Volumes = Teaspoon | Tablespoon | Slice | FluidOunces
> >      ... and I want to define an infix function '<+>', which adds
> together
> > amounts of food:
> >      (1, Slice, cake) <+> (1, Volume Tablespoon, sugar) <+> (32, Volume
> > FluidOunces, almondMilk)
> >      Which would return:
> >      (3200, Teaspoons)
> >      What is the most efficient way to define equivalency/conversion
> between
> > these measures?
> >      I remember an interesting method for celsius-farenheit conversion in
> > Higher-Order Perl, using function composition, but that was between only
> 2
> > units. I'd like something where I don't have to provide n^2 definitions.
>
> I wrote two blog posts on something that sounds related:
> http://therning.org/magnus/archives/354
> http://therning.org/magnus/archives/355
>
> Maybe they could help.
>
> /M
>
> --
> Magnus Therning                      OpenPGP: 0xAB4DFBA4
> email: magnus at therning.org   jabber: magnus at therning.org

So given
data Measure = M Volume Float
deriving (Show, Eq)
data Volume = Slice
| FluidOunce
| Tablespoon
| Teaspoon
deriving (Show, Eq)

Method one and two would be to convert all units to the smallest unit:
toTsp                           :: Measure -> Measure
[...]
toTsp (M Slice a)          = M Teaspoon (a * 350)
toTsp (M FluidOunce a) = M Teaspoon (a * 34)
[...]

Perform the addition, then convert it to a more appropriate measure (back to
Cake Slices...).
It seems that there's a potential loss of precision, if I have to for
example convert Megaliter measures to Teaspoons.

Method three involves using typeclasses to define common "meeting points"
for the units.
So, we say that if we're adding Slices and FluidOunces, both measures should
be converted to FluidOunces before the addition.
The problem there is that, given n measurement units, we have to declare n^2
typeclass instances by hand.

Another way that I can see to do this is to define a "chain" of conversions.
I'd then "convert down" only to the smallest-common unit.

So I could define
a series of "step-down"s and "step-up"s:

Given a largest-to-smallest ordering of Slice, FluidOunce, Tablespoon,
Teaspoon, I can define:

stepDown                           :: Measure -> Measure
stepDown (M Slice a)          = M FluidOunce (a * 120)
stepDown (M FluidOunce a) = M Tablespoon (a * 2)
stepUp   (M FluidOunce a)   = M Slice (a / 120)
stepUp   (M Tablespoon a)   = M FluidOunce (a / 2)
[...]

and then a function to "step" as far down as needed:

convertDown :: Measure -> Volume -> Measure
convertDown (M unit measure) goalUnit
| unit == goalUnit = M unit measure
| otherwise        = fun5 (stepDown (M unit measure))
goalUnit

And then if I wanted to add a Slice of cake to a Tablespoon of almond milk,
I'd have to find the smaller unit, convert down, perform the math and then
do a "convert up" operation.

An added benefit to this method is that defining a unit smaller than the
current smallest wouldn't involve revising the "base unit" measures.

This seems like a pretty common C.S. problem; I'm surprised I haven't been
able to find more resources about it.