
How about something like this?
import Data.Monoid
data Volumes = Teaspoon | Tablespoon | Slice | FluidOunces
type Quantity = (Int, Volumes)
instance Monoid Quantity where
mempty = (0, Teaspoon)
mappend a b = ((toTeaspoon a) + (toTeaspoon b), Teaspoon)
toTeaspoon :: Quantity -> Int
toTeaspoon (a, Teaspoon) = a
toTeaspoon (a, Tablespoon) = undefined
toTeaspoon (a, Slice) = undefined
toTeaspoon (a, FluidOunces) = undefined
-deech
On Fri, Mar 18, 2011 at 10:02 PM, Tom Murphy
On Fri, Mar 18, 2011 at 5:02 AM, Magnus Therning
wrote: On Thu, Mar 17, 2011 at 21:17, Tom Murphy
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! _______________________________________________ Beginners mailing list Beginners@haskell.org http://www.haskell.org/mailman/listinfo/beginners