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