Reputation: 3625
I want to create a data type which can take a tuple, of either length 2 or 3, and that should contain types deriving Num and Ord. Then, I want to pattern match on the length of this tuple in my type class instances so that I can decide which function to use based on the tuple length. Take a look at the non-compilable, pseudo-code below. There are at least two problems:
Dominates
is not a derivable class
I don't know how to get the tuple, e.g. to call fitnesses
, to get the variable out of the data type when pattern matching in the instance.
Code:
data Fits = I2 (Int, Int) | F2 (Float, Float) | I3 (Int, Int, Int) | F3 (Float, Float, Float)
data Ind = Ind { fitnesses :: Fits
, otherInfo :: String
} deriving (Dominates)
class Dominates a where
dominates :: a -> a -> bool
instance Dominates Ind where
dominates x@(_,_) y@(_,_) = x `dominates2` y -- takes two tuples of length 2
dominates x@(_,_,_) y@(_,_,_) = x `dominates3` y -- takes two tuples of length 3
Update:
data Ind = Ind { fitnesses :: Fits
, otherInfo :: String
}
instance Eq Ind where
(Ind{ fitnesses = I2 x@(a1,a2) }) == (Ind{ fitnesses = I2 y@(b1,b2) }) = indCmp2 x y == EQ
instance Ord Ind where
(Ind{ fitnesses = I2 x@(a1,a2) }) `compare` (Ind{ fitnesses = I2 y@(b1,b2) }) = indCmp2 x y
indCmp2 :: (Num a, Ord a) => (a, a) -> (a, a) -> Ordering
indCmp2 x y
| a0 < b0 = LT
| a0 > b0 = GT
-- Can assume (fst x) == (fst y) beneath
| a1 < b1 = LT
| a1 > b1 = GT
| a1 == b1 = EQ
where
a0 = fst x
a1 = snd x
b0 = fst y
b1 = snd y
This compiles. Why is it necessary to make an instance of Eq
, if I only want Ord
? Without the Eq-instance the compiler complains "no instance for (Eq Ind)".
Upvotes: 0
Views: 380
Reputation: 116174
You can provide instances for 2- and 3-tuples.
class Dominates a where
dominates :: a -> a -> bool
instance (Num a, Num b) => Dominates (a,b) where
dominates = dominates2 -- takes two tuples of length 2
instance (Num a, Num b, Num c) => Dominates (a,b,c) where
dominates = dominates3 -- takes two tuples of length 3
I wonder, however, if this is what you actually need.
You could instead need pattern match on all possible cases as follows. No classes are needed here.
dominates :: Ind -> Ind -> Bool
dominates (Ind{ fitnesses = I2 (a1,a2) })
(Ind{ fitnesses = I2 (b1,b2) }) = ...
dominates (Ind{ fitnesses = F2 (a1,a2) })
(Ind{ fitnesses = F2 (b1,b2) }) = ...
dominates (Ind{ fitnesses = I3 (a1,a2,a3) })
(Ind{ fitnesses = I3 (b1,b2,b3) }) = ...
dominates (Ind{ fitnesses = F3 (a1,a2,a3) })
(Ind{ fitnesses = F3 (b1,b2,b3) }) = ...
dominates _ _ = error "incompatible Ind values!"
Example:
data Ind = Ind { fitnesses :: Fits
, otherInfo :: String
} -- no deriving Ord here, we want to define it manually
instance Ord Ind where
(Ind{ fitnesses = I2 x }) `compare` (Ind{ fitnesses = I2 y }) = indCmp2 x y
-- other cases here
Upvotes: 2