Tom Murphy amindfv at gmail.com
Sat Mar 19 18:50:54 CET 2011

```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
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.

Tom

On Sat, Mar 19, 2011 at 12:11 AM, aditya siram <aditya.siram at 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 at gmail.com> wrote:
> > 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
> > (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