Hi All,

Here's what I have so far:
I haven't tried it with monoids but I'm intrigued...


data Measure = M Volume Float
  deriving (Show, Eq)

data Volume = Teaspoon
            | Tablespoon
            | FluidOunce
            | Slice
  deriving (Show, Eq, Ord, Bounded)                                  -- Note the Ord and Bounded typeclasses



stepDown :: Measure -> Measure                                      -- stepDown - convert Measures to one unit smaller
stepDown (M Slice a)      = M FluidOunce (a * 120)
stepDown (M FluidOunce a) = M Tablespoon (a * 2)
stepDown (M Tablespoon a) = M Teaspoon (a * 3)
stepUp   (M Teaspoon a)   = M Tablespoon (a / 3)              -- stepUp - convert Measures to one unit larger
stepUp   (M Tablespoon a) = M FluidOunce (a / 2)
stepUp   (M FluidOunce a) = M Slice (a / 120)
stepUp   (M Slice a)      = M Slice (a * 1)


convertDown :: Measure -> Volume -> Measure                 -- stepDown multiple times, to a desired unit
convertDown (M unit measure) goalUnit
                     | unit == goalUnit = M unit measure
                     | otherwise        = convertDown (stepDown (M unit measure))  goalUnit

convertUp all@(M unit measure)                                      -- stepUp to a unit which "makes sense" for the magnitude of the measure
            | (measure > 25) && (unit /= (maxBound :: Volume)) = convertUp $ stepUp all
            | otherwise    = all


(<+>) :: Measure -> Measure -> Measure                        -- do the addition
a@(M unitA measureA) <+> b@(M unitB measureB)
     | unitA == unitB = M unitA (measureA + measureB)
     | unitA > unitB  = convertUp $ ((convertDown a unitB) <+> b)
     | unitA < unitB  = convertUp $ (a <+> (convertDown b unitA))





This is fully functional:
*Main> (M Slice 30) <+> (M Teaspoon 3)
M Slice 30.004168
*Main> (M FluidOunce 57) <+> (M Slice 0.2)
M Slice 0.675
*Main> stepDown it
M FluidOunce 81.0


etc.




In convertUp, "25" could be replaced by a multiple of the unit "above" if I had a better data structure for keeping track of conversion amounts.
This is the major kludge that I see in my code right now. Having one stepUp function and one stepDown function, each of which read off a conversion list, or even some type of tree, would be more efficient.


Any resources, for solutions to this problem, would definitely be appreciated.
Again, I'm really surprised that I can't find very much at all, on what seems to be a common computing problem.

Thanks for your time.

Tom












On Sat, Mar 19, 2011 at 12:11 AM, aditya siram <aditya.siram@gmail.com> wrote:
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 <amindfv@gmail.com> wrote:
> 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!
> _______________________________________________
> Beginners mailing list
> Beginners@haskell.org
> http://www.haskell.org/mailman/listinfo/beginners
>
>