Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Performing algebra with newtypes based on integers Haskell

I'm having some trouble with performing simple addition, subtraction -- any kind of algebra really with Haskells newtype.

My definition is (show included so I can print them to console):

newtype Money = Money Integer deriving Show

What I'm trying to do is basically:

Money 15 + Money 5 = Money 20
Money 15 - Money 5 = Money 10
Money 15 / Money 5 = Money 3

And so on, but I'm getting

m = Money 15
n = Money 5
Main>> m-n

ERROR - Cannot infer instance
*** Instance   : Num Money
*** Expression : m - n

I can't find a clear and consise explanation as to how the inheritance here works. Any and all help would be greatly appreciated.

like image 586
Mats Avatar asked Dec 11 '22 04:12

Mats


2 Answers

Well Haskell can not add up two Moneys, since you never specified how to do that. In order to add up two as, the as should implement the Num typeclass. In fact newtypes are frequently used to specify different type instances, for example Sum and Product are used to define two different monoids.

You thus need to make it an instance of Num, so you have to define an instance like:

instance Num Money where
    Money a + Money b = Money (a+b)
    Money a - Money b = Money (a-b)
    Money a * Money b = Money (a*b)
    abs (Money a) = Money (abs a)
    signum (Money a) = Money (signum a)
    fromInteger = Money

Since (/) :: Fractional a => a -> a -> a is a member of the Fractional typeclass, this will give some problems, since your Money wraps an Integer object.

You can however implement the Integral typeclass such that it supports div. In order to do this, we however need to implement the Real and Enum typeclass. The Real typeclass requires the type to be implement the Ord, and since the Ord typeclass requires the object to be an instance of the Eq typeclass, we thus end up implementing the Eq, Ord, Real and Enum typeclass.

instance Eq Money where
    Money x == Money y = x == y

instance Ord Money where
    compare (Money x) (Money y) = compare x y

instance Real Money where
    toRational (Money x) = toRational x

instance Enum Money where
    fromEnum (Money x) = fromEnum x
    toEnum = Money . toEnum

instance Integral Money where
    toInteger (Money x) = x
    quotRem (Money x) (Money y) = (Money q, Money r)
        where (q, r) = quotRem x y

GeneralizedNewtypeDeriving

As @Alec says we can use a GHC extension named -XGeneralizedNewtypeDeriving.

The above derivations are quite "boring" here we each time "unwrap" the data constructor(s), perform some actions, and "rewrap" them (well in some cases either unwrapping or rewrapping are not necessary). Especially since a newtype actually does not exists at runtime (this is more a way to let Haskell treat the data differently, but the data constructor will be "optimized away"), it makes not much sense.

If we compile with:

ghc -XGeneralizedNewtypeDeriving file.hs

we can declare the Money type as:

newtype Money = Money Integer deriving (Show, Num, Enum, Eq, Ord, Real, Integral)

and Haskell will perform the above derivations for us. This is, to the best of my knowledge, a GHC feature, and thus other Haskell compilers do not per se (well they can of course have this feature) support this.

like image 158
Willem Van Onsem Avatar answered Dec 12 '22 17:12

Willem Van Onsem


You're missing a instance of how your money can add together, the clue was in the error Instance : Num Money.

So for addition in Haskell the Num type what is needed to add two things together as long you are dealing with numbers so let's make an instance of Num on Money:

newtype Money =
  Money Integer deriving Show

instance Num Money where
  Money a + Money b = Money $ a + b

-- Money 1 + Money 2 == Money 3

Notice that it returns Money, will let you research how you can get the number out of the type :)

like image 33
cmdv Avatar answered Dec 12 '22 18:12

cmdv