Yann Rolland
Yann Rolland

Reputation: 93

haskell fromInteger with multiple data constructors

I am looking for a solution that is quite generic I think. My goal is to find a clean way to do operations on Points (coordinates), either in 2D or 3D. my data Point is an instance of Num that gives functions to do basics maths operations.

data Point = Point2D Float Float
       | Point3D Float Float Float deriving Show

add :: Point -> Point -> Point
add (Point2D xa ya) (Point2D xb yb) = Point2D (xa+xb) (ya+yb)
add (Point3D xa ya za) (Point3D xb yb zb) = Point3D (xa+xb) (ya+yb) (za+zb)

divT (Point2D x y) v = Point2D (x / v) (y / v)
divT (Point3D x y z) v = Point3D (x / v) (y / v) (z / v)

fromIntegerP :: Integer -> Point
fromIntegerP v = Point2D (fromIntegral v) 0
--fromIntegerP v = Point3D (fromIntegral v) 0 0

instance Num Point where
    (+) = add
    fromInteger = fromIntegerP

p2D1 = Point2D 1.0 2.0
p2D2 = Point2D 4.0 5.0
p3D1 = Point3D 1.0 2.0 3.0
p3D2 = Point3D 6.0 6.0 6.0

main = do
    putStrLn . show $ sum [p2D1,p2D2]
    putStrLn . show $ sum [p3D1,p3D2]

This code outputs :

Point2D 5.0 7.0
*** Exception: pointStackoverflow.hs:(5,1)-(6,75): Non-exhaustive patterns in function add

... because every Point.fromInteger produces a Point2D, even if we expect a Point3D. I would like to have a way to say in "instance Num Point" that if a Point2D is needed then fromInteger is fromIntegerToPoint2D else fromIntegerToPoint3D. But I don't know how to make a choice based on the return type.

Any hint ? Thank you

Upvotes: 2

Views: 137

Answers (2)

Yann Rolland
Yann Rolland

Reputation: 93

Here is one solution :

class Point a where
    add :: a -> a -> a
    fromIntegerP :: Integer -> a

data Point2D = Point2D Float Float deriving Show
data Point3D = Point3D Float Float Float deriving Show

instance Point Point2D where
    add (Point2D xa ya) (Point2D xb yb) = Point2D (xa+xb) (ya+yb)
    fromIntegerP v = Point2D (fromIntegral v) 0

instance Point Point3D where    
    add (Point3D xa ya za) (Point3D xb yb zb) = Point3D (xa+xb) (ya+yb) (za+zb)
    fromIntegerP v = Point3D (fromIntegral v) 0 0

instance Num Point2D where
    (+) = add
    fromInteger = fromIntegerP

instance Num Point3D where
    (+) = add
    fromInteger = fromIntegerP

p2D1 = Point2D 1.0 2.0
p2D2 = Point2D 4.0 5.0

p3D1 = Point3D 1.0 2.0 3.0
p3D2 = Point3D 6.0 6.0 6.0

main = do
    putStrLn . show $ sum [p2D1,p2D2]
    putStrLn . show $ sum [p3D1,p3D2]

I am still looking for improvements (instance Num Point2D where and instance Num Point3D where) but it works !

Upvotes: 0

ErikR
ErikR

Reputation: 52029

You want to make Point2D and Point3D be different types, not different constructors for the same type.

Start with this and fill out the definitions:

data Point2D = Point2D Float Float
data Point3D = Point3D Float Float Float

instance Num Point2D where
    fromInteger = ...
    (+) = ...

instance Num Point3D where
    fromInteger = ...
    (+) = ...

Upvotes: 2

Related Questions