Search code examples
haskellgenericstypesnumbersgeneric-programming

How to get a generic average that works with vectors to compile in Haskell?


I tried:

import Data.VectorSpace     -- ^/
import Data.AdditiveGroup   -- sumV
import Data.Foldable        -- length

avg :: (Foldable f, VectorSpace a) => f a -> a
avg xs = sm ^/ sz    -- could be just ^*(1/sz)
 where sm = sumV xs
       sz = fromIntegral (length xs)

and got:

Distancesumcurves.hs:11:10: error:
    • Could not deduce (Fractional (Scalar a))
        arising from a use of ‘^/’
      from the context: (Foldable f, VectorSpace a)
        bound by the type signature for:
                   avg :: (Foldable f, VectorSpace a) => f a -> a
        at Distancesumcurves.hs:10:1-46
    • In the expression: sm ^/ sz
      In an equation for ‘avg’:
          avg xs
            = sm ^/ sz
            where
                sm = sumV xs
                sz = fromIntegral (length xs)

Distancesumcurves.hs:13:13: error:
    • Could not deduce (Num (Scalar a))
        arising from a use of ‘fromIntegral’
      from the context: (Foldable f, VectorSpace a)
        bound by the type signature for:
                   avg :: (Foldable f, VectorSpace a) => f a -> a
        at Distancesumcurves.hs:10:1-46
    • In the expression: fromIntegral (length xs)
      In an equation for ‘sz’: sz = fromIntegral (length xs)
      In an equation for ‘avg’:
          avg xs
            = sm ^/ sz
            where
                sm = sumV xs
                sz = fromIntegral (length xs)

Of course I could just write one specifically for vectors, and get on with my life, but then whats the point of the generic features of Haskell. In a less pedantic language I would be done with it already, and it would work just fine, and it would be reusable. In Haskell I have to write a question here or regress to code-duplication. I consider this a failure of the language.

Its not the first time I got frustrated with Haskell generics. I am past the level of the book Programming in Haskell by Graham Hutton. What should be the next step/book/article for me? So that I dont get into arguments with the compiler, when I know what I want, and it is correct. Its just language-technical skill I lack in this case. Feel free to correct me if I am wrong.


edit:

seems to work in context

approximateCurve :: [Point] -> Path
approximateCurve pts = [] -- TODO
 where center = avg pts
       avg vs = (sum vs) ^/ (fromIntegral (length vs))
       sum = foldr (^+^) (0,0)

Solution

  • In this case, I would add the additional constraint suggested in the error message, to get avg :: (Foldable f, VectorSpace a, Fractional (Scalar a)) => f a -> a.

    Fractional implies Num, so no need to mention Num. Also no harm if you add it - the function is not made less generic.

    As far as general advice, I think writing lots of generic functions, guided by the type errors, is the best way. Often adding the suggested constraints is enough; other times you really want the more-general signature, and need to figure out which function is bringing in the unwanted constraint.