Necrobious'

Thursday, March 19, 2009

A fun example of Haskell's newtype

Haskell's newtype keyword allows you to hide an existing type behind a new type definition. in working with them I though of a neat example that would help illustrate how they work:

{-# LANGUAGE GeneralizedNewtypeDeriving #-}

newtype Fahrenheit = Fahrenheit Float
deriving (Eq, Ord, Show, Num, Fractional)

newtype Celsius = Celsius Float
deriving (Eq, Ord, Show, Num, Fractional)

far2cel :: Fahrenheit -> Celsius
far2cel (Fahrenheit far) = Celsius $ (5 / 9) * (far - 32)

cel2far :: Celsius -> Fahrenheit
cel2far (Celsius cel) = Fahrenheit $ (cel * (9 / 5)) + 32

We're declaring two "new types" one named Fahrenheit and the other named Celsius, both are really just Floats. Then we declare two conversion functions, far2cel and cel2far, to handle marshaling a Fahrenheit temperature to a Celsius. We are using the *, -, and + operators from the Num class and the / operator from Fractional, by declaring our Fahrenheit and Celsius newtypes to derive from Fractional and from Num in conjunction with the -XGeneralizedNewtypeDeriving GHC option. Neat

5 Comments:

  • You don't really make use of the deriving Num, Fractional because you apply the operators on cel and far, which have type Float, not Fahrenheit or Celsius.

    By Blogger thu, at 11:40 AM  

  • The really nice thing about this is, the compiler will now complain with a type error if you accidentally mix your units!

    By Blogger Brent, at 2:42 PM  

  • Brent got it right, the point about this is safety. Mix match is impossible ans still the solution isn't heavy.

    In Java, to be safe you would need to create a class for Celsius, another for Farenheit, etc...

    By Blogger Unknown, at 2:12 AM  

  • Note, though, that the Num instance for Farhrenheit requires that when you want to multiply or divide stuff, you have to put in Fahrenheit and get Fahrenheit. For example:
    > (medianTemp - minTemp) / (maxTemp - minTemp)
    will have type Fahrenheit as well, although it is actually a unitless fraction.

    There are packages for better handling of physical units, though.

    By Blogger nomeata, at 9:25 AM  

  • I have to deal with things like this in my day-to-day programming, and I generally find it easier to use an abstract data type, with a common internal format:

    > module Temp (
    > Temp, degC, degF,
    > ) where
    >
    > newtype Temp = DegC Double deriving(Eq, Num, Show)
    >
    > degC, degF :: Double -> Temp
    > degC = DegC
    > degF n = DegC $ (5/9)*(n-32)


    Note that the module does not export the data constructor for temp; you must use the degC and degF functions to create values of this type, which forces the programmer to explicitly say what type his units are when using constants:

    > degF 55 - degC 8

    If you use -XPostfixOperators, you can even use these in postfix format, though I'm not sure it's any less clunky:

    > (55 `degF`) - (8 `degC`)

    By Anonymous Anonymous, at 8:14 AM  

Post a Comment



<< Home