How can I resolve this class instance error in the following very simple function?
I'd like to implement a simple Restricted
Typeclass that has two functions, lowerBound
and upperBound
, that each take a Restricted
instance and return a Num
.
class Restricted r where
lowerBound :: (Num n) => r -> n
upperBound :: (Num n) => r -> n
I'm trying to create an instance of this typeclass with an OrthogonalClass
data type.
instance Restricted OrthogonalClass where
lowerBound p = orthogonalLowerBound p
upperBound p = orthogonalUpperBound p
where
orthogonalLowerBound :: (Num a) => OrthogonalClass -> a
orthogonalLowerBound Legendre = a
where a = (-1.0) :: Double
-- orthogonalLowerBound Hermite = NegInf
(-1.0)
is a Double
and it should be an acceptable return value as a Double
is an instance of Num
. However, I get the following error from the compiler:
Polynomials.hs:20:33: error:
• Couldn't match expected type ‘a’ with actual type ‘Double’
‘a’ is a rigid type variable bound by
the type signature for:
orthogonalLowerBound :: forall a. Num a => OrthogonalClass -> a
at Polynomials.hs:19:1-55
• In the expression: a
In an equation for ‘orthogonalLowerBound’:
orthogonalLowerBound Legendre
= a
where
a = (- 1.0) :: Double
• Relevant bindings include
orthogonalLowerBound :: OrthogonalClass -> a
(bound at Polynomials.hs:20:1)
|
20 | orthogonalLowerBound Legendre = a
| ^
I can fix this by changing (-1.0)
to (-1)
. However, this doesn't help me when I uncomment the second pattern of orthogonalLowerBound
that matches Hermite
and returns NegInf
, which is a constructor of a data class Inf
that is an instance of a number.
Here lies my confusion. A Double
is an instance of a Num
, so why can't I return a Double
in my function orthogonalLowerBound
? Why can't I return an Inf
which is an instance of a Num
?
What am I missing?
Here's the complete definition of OrthogonalClass
and Inf
in case you want extra information
data OrthogonalClass = Legendre | Laguerre | Hermite | Tchebychev
-- Playing around with infinity
data Inf = NegInf | PosInf | Undef | Finite deriving (Show)
instance Num Inf where
(+) a b = infAdd a b
(-) a b = infSub a b
(*) a b = infMult a b
signum a = infSignum a
abs a = infAbs a
fromInteger a = infFromInt a
infAdd :: Inf -> Inf -> Inf
infAdd NegInf NegInf = NegInf
infAdd PosInf PosInf = PosInf
infAdd PosInf NegInf = Undef
infAdd NegInf PosInf = Undef
infAdd Undef _ = Undef
infAdd _ Undef = Undef
infAdd NegInf Finite = NegInf
infAdd PosInf Finite = PosInf
infAdd Finite Finite = Finite
infSub :: Inf -> Inf -> Inf
infSub NegInf NegInf = Undef
infSub PosInf PosInf = Undef
infSub PosInf NegInf = PosInf
infSub NegInf PosInf = NegInf
infSub Undef _ = Undef
infSub _ Undef = Undef
infSub NegInf Finite = NegInf
infSub PosInf Finite = PosInf
infSub Finite Finite = Finite
infMult :: Inf -> Inf -> Inf
infMult NegInf NegInf = PosInf
infMult PosInf PosInf = PosInf
infMult PosInf NegInf = NegInf
infMult NegInf PosInf = NegInf
infMult Undef _ = Undef
infMult _ Undef = Undef
infMult NegInf Finite = NegInf
infMult PosInf Finite = PosInf
infMult Finite Finite = Finite
infAbs :: Inf -> Inf
infAbs NegInf = PosInf
infAbs PosInf = PosInf
infAbs Undef = Undef
infAbs Finite = Finite
infSignum :: (Num a) => Inf -> a
infSignum NegInf = (-1)
infSignum PosInf = 1
infSignum Undef = 0
infSignum Finite = 0
infFromInt :: (Integral i) => i -> Inf
infFromInt x = Finite
Solution 1:
This is a very common confusion about the direction of generic types.
In short: it's the caller of the function who chooses the generic type, not the implementer.
If you have a function with this signature:
orthogonalLowerBound :: (Num a) => OrthogonalClass -> a
That signature says: hey you, whoever calls my function! Choose a type. Any type. Let's call it a
. Now make sure there is an instance Num a
. Done? Great! Now I can return you a value of that type a
.
This type signature is a promise to whoever calls the function, and you, the implementer of the function, must fulfill that promise. Whatever type the caller chooses, you must return a value of that type.
And the caller may choose any type that has a Num
instance, for example:
o :: OrthogonalClass
o = ...
x :: Int
x = orthogonalLowerBound o
y :: Decimal
y = orthogonalLowerBound o
The solution? If your function should return a Double
, just say so in its type signature:
orthogonalLowerBound :: OrthogonalClass -> Double
Of course, in this case you can't return Inf
, and that is as it should be: a function cannot return different types depending on values of arguments. This feature is called "dependent types" and Haskell doesn't have direct support for it.
If you do need infinity, the Double
type does contain it too, and there are ways to obtain it as a value. For example, take a look at infinity
from the ieee754
package