0
votes

I'm implementing a notion of inner product that's general over the container and numerical types. The definition states that the return type of this operation is a (non-negative) real number. One option (shown below) is to write all instances by hand, for each numerical type (Float, Double, Complex Float, Complex Double, Complex CFloat, Complex CDouble, etc.). The primitive types aren't many, but I dislike the repetition. Another option, or so I thought, is to have a parametric instance with a constraint such as RealFloat (which represents Float and Double).

{-# language MultiParamTypeClasses, TypeFamilies, FlexibleInstances #-}
module Test where

import Data.Complex

class Hilbert c e where
  type HT e :: *
  dot :: c e -> c e -> HT e


instance Hilbert [] Double where
  type HT Double = Double
  dot x y = sum $ zipWith (*) x y

instance Hilbert [] (Complex Double) where
  type HT (Complex Double) = Double
  a `dot` b = realPart $ sum $ zipWith (*) (conjugate <$> a) b

Question

Why does the instance below not work ("Couldn't match type e with Double.. expected type HT e, actual type e")?

instance RealFloat e => Hilbert [] e where
  type HT e = Double
  dot x y = sum $ zipWith (*) x y
1
sum returns an e (since x and y have type [e]) but dot should be returning a HT e = Double. Somehow, you'll need to convert e to a Double.Alec

1 Answers

3
votes

Well, that particular instance doesn't work because the sum only yields an e, but you want the result to be Double. As e is constrained to RealFrac, this is easy to fix though, as any Real (questionable though is is mathematically) can be converted to a Fractional:

  dot x y = realToFrac . sum $ zipWith (*) x y

However, that generic instance prevents you from also defining complex instances: with instance RealFloat e => Hilbert [] e where you cover all types, even if they aren't really real numbers. You could still instantiate Complex as an overlapping instance, but I'd rather stay away from those if I could help it.

It's also questionable if such vectorspace classes should be defined on * -> * at all. Yes, linear also does it this way, but IMO parametricity doesn't work in our favour in this application. Have you checked out the vector-space package? Mind, it isn't exactly complete for doing serious linear algebra; that's a gap I hope to fill with my linearmap-category package.