Search code examples
haskelltypeclasstype-declaration

How do I use typeclasses in Haskell functions?


I'm currently trying to teach myself Haskell with Learn You A Haskell, and so to test myself I'm trying to write a function that takes a list of numbers and returns the average value.

listlen :: [a] -> Int
listlen [] = 0
listlen (x:xs) = 1 + listlen xs

avg :: (Num a) => [a] -> a
avg [] = error "Cannot average list of length 0"
avg l = ((foldr (+) 0 l) `div` (listlen l))

main = putStrLn (show (avg [1,2,3,4,5]))

However, when I run this code, I get this error message:

test.hs:7:10: error:
    • Couldn't match expected type ‘a’ with actual type ‘Int’
      ‘a’ is a rigid type variable bound by
        the type signature for:
          avg :: forall a. Num a => [Int] -> a
        at test.hs:5:8
    • In the expression: ((foldr (+) 0 l) `div` (listlen l))
          In an equation for ‘avg’:
          avg l = ((foldr (+) 0 l) `div` (listlen l))
    • Relevant bindings include
        avg :: [Int] -> a (bound at test.hs:6:1)

I've been tinkering with the signature and the only way I can get it to work is to make the types into actual types, e.g. avg :: [Int] -> Int. I would greatly appreciate any advice.


Solution

  • The problem is from here:

    avg l = ((foldr (+) 0 l) `div` (listlen l))
    

    div is defined over Integrals, not just Nums. Also, div floors the result. Normal division is (/), which is defined over Fractionals.

    Also (more directly) from here:

    listlen :: [a] -> Int
    listlen [] = 0
    listlen (x:xs) = 1 + listlen xs
    

    listlen returns Int, which makes div have type of Int -> Int -> Int.

    This should work:

    listlen :: (Num n) => [a] -> n
    listlen [] = 0
    listlen (x:xs) = 1 + listlen xs
    
    avg :: (Fractional a) => [a] -> a
    avg [] = error "Cannot average list of length 0"
    avg l = foldr (+) 0 l / listlen l
    

    Or, with some functions from Prelude:

    avg :: (Fractional a) => [a] -> a
    avg [] = error "Cannot average list of length 0"
    avg l = sum l / fromIntegral (length l)