user3248346
user3248346

Reputation:

How define an average function when dealing with monads

I have list of type

PrimMonad m => m [Double]

To calculate the average I defined the following functions:

sum' :: PrimMonad m => m [Double] -> m Double
sum' xs = (sum <$> xs)

len' :: PrimMonad m => m [Double] -> m Int
len' xs = length <$> xs

avg :: PrimMonad m => m [Double] -> m Double
avg xs = (sum' xs) / (fromIntegral $ len' xs)

However, I am having problems with the avg function. I get the following errors:

    Could not deduce (Fractional (m Double)) arising from a use of ‘/’ …                                                                                           
        from the context (PrimMonad m)                                                                                                                                                                          
          bound by the type signature for                                                                                                                                                                       
                     avg :: PrimMonad m => m [Double] -> m Double                                                                                                                                               
          at                                                                                                                               
        In the expression: (sum' xs) / (fromIntegral $ len' xs)                                                                                                                                                 
        In an equation for ‘avg’:                                                                                                                                                                               
            avg xs = (sum' xs) / (fromIntegral $ len' xs)                                                                                                                                                       
  Could not deduce (Integral (m Int)) …                                                                                                                          
          arising from a use of ‘fromIntegral’                                                                                                                                                                  
        from the context (PrimMonad m)                                                                                                                                                                          
          bound by the type signature for                                                                                                                                                                       
                     avg :: PrimMonad m => m [Double] -> m Double                                                                                                                                               
          at                                                                                                                               
        In the expression: fromIntegral                                                                                                                                                                         
        In the second argument of ‘(/)’, namely ‘(fromIntegral $ len' xs)’                                                                                                                                      
        In the expression: (sum' xs) / (fromIntegral $ len' xs)                                                                                                                                                 
    Compilation failed.

What do I need to do get this simple function defined?

Upvotes: 0

Views: 175

Answers (2)

epsilonhalbe
epsilonhalbe

Reputation: 15949

the error you are facing is twofold

  1. you try to divide two monadic values
  2. you are using fromIntegral on a monadic value.

now to the solving part - easiest would be to use do notation to circumvent the whole problem

for simplicity's sake I copied an avg function from getting-average-of-calculated-list-haskell

{-# LANGUAGE BangPatterns #-}
import Data.List

avg :: (Integral a, Fractional b) => [a] -> b
avg xs = g $ foldl' c (0,0) xs
 where
   c (!a,!n) x = (a+x,n+1)
   g (a,n) = fromIntegral a / fromIntegral n

monAvg :: (Monad m, Integral a, Fractional b) => m [a] -> m b
monAvg mxs = do xs <- mx
                return $ avg xs

but this is maybe not the thing you want it is too specialised.

The minimal thing we need is only Functor - so fmap avg solves your problem as well.

if you want to keep your functions - then you need <*> from Applicatives to combine.

appAvg :: Applicative m => m [Double] -> m Double
appAvg xs = (/) <$> sum xs <*> (fromIntegral <$> len' xs)

Upvotes: 1

chi
chi

Reputation: 116139

The problem lies on / wanting a number like Double, not a number inside a monad like m Double.

A naive fix could be

avg :: PrimMonad m => m [Double] -> m Double
avg xs = (/) <$> sum' xs <*> (fromIntegral <$> len' xs)

but this is unsatisfactory, since it will run the monadic action xs twice.

I'd rather use something like

avg :: PrimMonad m => m [Double] -> m Double
avg xs = (\ys -> sum ys / fromIntegral (length ys)) <$> xs

I would also avoid naming xs a monadic action, since xs can easily be taken for a plain list. This is however a matter of personal preference.

Even better, I'd define a non-monadic average first:

avg :: [Double] -> Double
avg xs = sum xs / fromIntegral (length xs)

avgM :: PrimMonad m => m [Double] -> m Double
avgM = fmap avg

Since avgM is so short, I would probably omit its definition, and directly call fmap avg where needed.

Upvotes: 7

Related Questions