简体   繁体   中英

New type class with instance definition generic on tuples of any length

Is it possible to use a GHC extension to define a new type class that generalizes to tuples of arbitrary length?

There have been a few questions already about the behavior of builtin classes in Prelude and Base (some classes support up to 15-element tuples, some up to 7) and about the (non-)possibility of extending these classes.

Prelude and Base behavior: Haskell Tuple Size Limit

extending Show with new definitions: Extend a Show instance to a Tuple of any size

I'm asking a slightly different question. If I'm making an entirely new type class, is it possible to add an instance rule that handles tuples of arbitrary length (possibly using a GHC extension)?

Here's an example of such a class called PartialOrder. I want to allow tuples of arbitrary size to be partially compared using the following rule

(a,b ... , z) <= (a1,b1, ... , z1) iff (a <= a1) && (b <= b1) && ... && (z <= z1)

Here's my first stab at a definition using the traditional "define the class for tuples up to some arbitrary size" approach.

Is there a GHC extension that can be used to write instance definitions that cover tuples of arbitrary length?

I think I can use Template Haskell or an external program to generate definitions in advance, but not generate them on demand like C++ templates.

-- Sets equipped with the (is_subset) operation are an example of a
-- partial order.
--
-- {} < {a}      less than
-- {} = {}       equal to
-- {a, b} > {b}  greater than
-- {a} ~ {b}     incomparable
--
-- in order to define a partial order we need a definition of (<=)

data PartialOrdering = POLessThan | POGreaterThan | POEqual | POIncomparable deriving (Eq, Show)

class PartialOrder a where
    lessThanEq :: a -> a -> Bool

instance PartialOrder PartialOrdering where
    lessThanEq POIncomparable _ = False
    lessThanEq _ POIncomparable = False

    -- with incomparables dealt with...
    lessThanEq POLessThan _ = True

    lessThanEq POEqual POLessThan = False
    lessThanEq POEqual _ = True

    lessThanEq POGreaterThan POGreaterThan = True
    lessThanEq POGreaterThan _ = False


-- note this is different from the semantics for Ord applied to tuples,
-- which uses lexicographic ordering.
--
-- (a,b) is less than or equal to (c,d) iff
-- a <= b and c <= d

-- 2 element tuple
instance (PartialOrder a, PartialOrder b) => PartialOrder (a, b) where
    lessThanEq (a,b) (c,d) = (lessThanEq a c) && (lessThanEq b d)

-- 3 element tuple
instance (PartialOrder a, PartialOrder b, PartialOrder c) => PartialOrder (a, b, c) where
    lessThanEq (a,b,c) (d,e,f) = (lessThanEq a d) && (lessThanEq b e) && (lessThanEq c f)

-- 4 element tuple
instance (PartialOrder a, PartialOrder b, PartialOrder c, PartialOrder d) => PartialOrder (a, b, c, d) where
    lessThanEq (a,b,c,d) (e,f,g,h) = (lessThanEq a e) && (lessThanEq b f) && (lessThanEq c g) && (lessThanEq d h)


-- etc.


main = putStrLn "hi"

The tuple types in Haskell don't really have any awareness of each other. Thankfully, for your particular case, you can solve your problem by using GHC.Generics . Then, you will actually be able to derive your PartialOrder class for any product type, not just tuples.

{-# LANGUAGE TypeOperators, DefaultSignatures, FlexibleContexts, 
             StandaloneDeriving, DeriveAnyClass
  #-}

import GHC.Generics
import Data.Function (on)

data PartialOrdering
  = POLessThan | POGreaterThan | POEqual | POIncomparable deriving (Eq, Show)

class PartialOrder a where
  lessThanEq :: a -> a -> Bool

  default lessThanEq :: (Generic a, GPartialOrder (Rep a)) => a -> a -> Bool
  lessThanEq = gLessThanEq `on` from


-- | Helper generic version of your class
class GPartialOrder f where
  gLessThanEq :: f a -> f a -> Bool

-- | Product types
instance (GPartialOrder a, GPartialOrder b) => GPartialOrder (a :*: b) where
  gLessThanEq (a1 :*: b1) (a2 :*: b2) = gLessThanEq a1 a2 && gLessThanEq b1 b2

-- | Unary type (empty product)
instance GPartialOrder U1 where
  gLessThanEq U1 U1 = True

-- | Meta information on type
instance (GPartialOrder a) => GPartialOrder (M1 i c a) where
  gLessThanEq (M1 a1) (M1 a2) = gLessThanEq a1 a2

-- | Single type
instance (PartialOrder a) => GPartialOrder (K1 i a) where
  gLessThanEq (K1 x1) (K1 x2) = lessThanEq x1 x2

With all that set up, one can automatically derive your class (with -XDeriveAnyClass enabled) if one derives Generic (which can be done with -XDeriveGeneric ). Tuples types are already instances of generics, so with -XStandaloneDeriving , you can retro-actively derive instances of Partial Order . All of the below work

deriving instance (PartialOrder a, PartialOrder b) => PartialOrder (a,b)
deriving instance (PartialOrder a, PartialOrder b, PartialOrder c) => PartialOrder (a,b,c)
-- and so on...

data MyProduct a b = MyProduct a b deriving (Generic, PartialOrder) 

After that, you can use your class, as expected:

ghci> (POLessThan, POLessThan) `lessThanEq` (POEqual, POEqual)
True
ghci> (POLessThan, POEqual) `lessThanEq` (POEqual, POEqual)
True
ghci> (POLessThan, POEqual) `lessThanEq` (POEqual, POLessThan)
False

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM