Reputation: 1732
I'm trying to implement the Cantor Pairing Function, as an instance of a generic Pair typeclass, as so:
module Pair (Pair, CantorPair) where
-- Pair interface
class Pair p where
pi :: a -> a -> p a
k :: p a -> a
l :: p a -> a
-- Wrapper for typing
newtype CantorPair a = P { unP :: a }
-- Assume two functions with signatures:
cantorPair :: Integral a => a -> a -> CantorPair a
cantorUnpair :: Integral a => CantorPair a -> (a, a)
-- I need to somehow add an Integral a constraint to this instance,
-- but I can't work out how to do it.
instance Pair CantorPair where
pi = cantorPair
k = fst . cantorUnpair
l = snd . cantorUnpair
How can I add the appropriate Integral constraint to the instance? I have a vague feeling I might need to modify the Pair interface itself, but not sure how to go about this.
Upvotes: 3
Views: 729
Reputation: 152682
If you have access to the class definition, you can add an Integral
constraint to the pi
, k
, and l
methods. This is a bit unsatisfactory, though: there's nothing saying that Integral
is going to be the right constraint for all instances, and you after all don't want to reject some instances just because you didn't have enough foresight. So, here's one generalization: we'll allow the constraint to vary in each instance.
{-# LANGUAGE ConstraintKinds, TypeFamilies #-}
import GHC.Exts
newtype CantorPair a = P { unP :: a }
cantorPair :: Integral a => a -> a -> CantorPair a
cantorUnpair :: Integral a => CantorPair a -> (a, a)
cantorPair = undefined
cantorUnpair = undefined
class Pair p where
type Ctxt p a :: Constraint
pi :: Ctxt p a => a -> a -> p a
k :: Ctxt p a => p a -> a
l :: Ctxt p a => p a -> a
instance Pair CantorPair where
type Ctxt CantorPair a = Integral a
pi = cantorPair
k = fst . cantorUnpair
l = snd . cantorUnpair
-- just for fun
data DataPair a = DataPair a a
instance Pair DataPair where
type Ctxt DataPair a = ()
pi = DataPair
k (DataPair a _) = a
l (DataPair _ a) = a
-- this one made GHC panic! neat, I get to file a bug
data Unit a = Unit
instance Pair Unit where
type Ctxt Unit a = a ~ ()
pi _ _ = Unit
k _ = ()
l _ = ()
Upvotes: 2
Reputation: 523154
This solution uses type families, so you need -XTypeFamilies
. We put the type constraint on the type itself, instead of the type constructor:
class Pair p where
type First p :: *
type Second p :: *
pi :: First p -> Second p -> p
k :: p -> First p
l :: p -> Second p
and then we create the instances like:
instance Integral a => Pair (CantorPair a) where
type First (CantorPair a) = a
type Second (CantorPair a) = a
pi = cantorPair
k = fst . cantorUnpair
l = snd . cantorUnpair
instance Pair (a, b) where
type First (a, b) = a
type Second (a, b) = b
pi = (,)
k = fst
l = snd
Upvotes: 1
Reputation: 68152
Do you want all pairs to always contain integral elements? In this case, you could add the constraint to the signatures of the methods:
class Pair p where
pi :: Integral i => i -> i -> p i
k :: Integral i => p i -> i
l :: Integral i => p i -> i
This will make you pair class less general but will ensure that your CantorPair
type could be a part of it.
If you want to keep your Pair
class somewhat general, you could use a multi parameter type class. (This will require two extensions: MultiParamTypeClasses
and FlexibleInstances
.)
class Pair p a where
pi :: a -> a -> p a
k :: p a -> a
l :: p a -> a
instance Integral i => Pair CantorPair i where
pi = cantorPair
k = fst . cantorUnpair
l = snd . cantorUnpair
I don't know if this is necessarily the best option from a design standpoint, but it's a good way to learn about how multiparamter type classes work. (Which, admittedly, is fairly simple.)
Upvotes: 1