[Haskell-beginners] Unit conversion
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
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 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
> >> 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 at haskell.org
> > http://www.haskell.org/mailman/listinfo/beginners
> >
> >
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://www.haskell.org/pipermail/beginners/attachments/20110319/fe8dfa1b/attachment.htm>
More information about the Beginners
mailing list