tsorn
tsorn

Reputation: 3625

Haskell pattern match on tuple length in instances

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:

  1. Dominates is not a derivable class

  2. 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

Answers (1)

chi
chi

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

Related Questions