On Fri, Mar 18, 2011 at 5:02 AM, Magnus Therning
<magnus@therning.org> wrote:
On Thu, Mar 17, 2011 at 21:17, Tom Murphy <
amindfv@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@therning.org jabber: magnus@therning.org
twitter: magthe http://therning.org/magnus
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
(warning: extreme kludge ahead)
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.
Thanks for your time!
Tom
P.S. Thank you, Magnus, for those resources!