I'm curious about what kind of "overloading" can be accomplished in Haskell's type classes via "FlexibleInstances".
As a simple test, here is a case of an AdjusterType datatype. It defines an adjust
operation that will add a different amount to its value based on whether it contains an Integer or a Double:
{-# LANGUAGE FlexibleInstances #-}
class Adjustable a where
adjust :: a -> Double
data AdjusterType a = Adjuster a
deriving Show
instance Adjustable (AdjusterType Integer) where
adjust (Adjuster a) = fromIntegral (a + 20)
instance Adjustable (AdjusterType Double) where
adjust (Adjuster a) = a + 0.04
That much works as expected:
Prelude> adjust (Adjuster (1000 :: Integer))
1020.0
Prelude> adjust (Adjuster (3 :: Double))
3.04
Is it possible to make the Integer version of adjust
return an Integer, and the Double version return a Double?
Generalizing the signature of adjust
and removing the fromIntegral
on the integer case doesn't work:
class Adjustable a where
adjust :: Num n => a -> n
instance Adjustable (AdjusterType Integer) where
adjust (Adjuster a) = a + 20
This produces an error saying that "n" is a rigid type variable that doesn't match Integer:
Couldn't match expected type ‘n’ with actual type ‘Integer’
‘n’ is a rigid type variable bound by
the type signature for adjust :: Num n => AdjusterType Integer -> n
Relevant bindings include
adjust :: AdjusterType Integer -> n
In the first argument of ‘(+)’, namely ‘a’
In the expression: a + 20
What type was it expecting here that Integer isn't matching...or would no type actually work and it's just a weird error message? (n is lowercase, so presumably it knows it's not a datatype)
Type constraints in the instance specifications also don't appear to participate in the matching resolution:
instance Integral i => Adjustable (AdjusterType i) where
adjust (Adjuster a) = fromIntegral (a + 20)
instance RealFloat r => Adjustable (AdjusterType r) where
adjust (Adjuster a) = a + 0.04
So these act like duplicates, as if they were both Adjustable (AdjusterType x))
. The constraint only applies after the resolution is done.
Is there any way to provide an overloaded behavior like above to a type class, or must it always be to a specific instance?
Is it possible to make the Integer version of adjust return an Integer, and the Double version return a Double?
You can make the Adjustable
type class accept two type parameters instead of one, so it will know what's inside the AdjusterType
:
{-# LANGUAGE MultiParamTypeClasses #-}
class Adjustable f a where
adjust :: f a -> a
Then the instances should be:
instance Adjustable AdjusterType Int where
adjust (Adjuster a) = a + 20
instance Adjustable AdjusterType Double where
adjust (Adjuster a) = a + 0.04
And some results from ghci:
> :set +t
> adjust (Adjuster (100 :: Int))
< 120
< it :: Int
> adjust (Adjuster (100 :: Double))
< 100.04
< it :: Double
What type was it expecting here that Integer isn't matching...or would no type actually work and it's just a weird error message?
The return type of adjust
is of type forall n . Num n => n
, a polymorphic type with a single constraint Num
, so your function returning a concrete type won't type check. Wrap your function with fromIntegral
will solve the problem since fromIntegral :: (Integral a, Num b) => a -> b
.
Is there any way to provide an overloaded behavior like above to a type class, or must it always be to a specific instance?
If you expect the function to behave differently for every distinct type, yes you have to add an instance for each. You may add some default behavior though, by restricting the type parameters of the class:
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE MultiParamTypeClasses #-}
class Extract f where
extract :: f a -> a
class (Extract f, Functor f, Num a) => Adjustable f a where
adjust :: f a -> a
adjust = extract . fmap (+20)
data AdjusterType a = Adjuster a
deriving (Functor)
instance Extract AdjusterType where
extract (Adjuster a) = a
instance Adjustable AdjusterType Int where
-- don't have to write any code here
A solution using type families, in particular associated data types, is the following:
{-# LANGUAGE TypeFamilies, FlexibleInstances #-}
class Adjustable a where
type Elem a :: *
adjust :: a -> Elem a
data AdjusterType a = Adjuster a
deriving (Show)
instance Adjustable (AdjusterType Integer) where
type Elem (AdjusterType Integer) = Integer
adjust (Adjuster a) = a + 20
instance Adjustable (AdjusterType Double) where
type Elem (AdjusterType Double) = Double
adjust (Adjuster a) = a + 0.04
main = do
let x = Adjuster 1 :: AdjusterType Integer
y = Adjuster 1 :: AdjusterType Double
print $ adjust x
print $ adjust y
It compiles and the output is:
21
1.04