如何在处理monad时定义一个平均函数

时间:2016-05-28 18:44:23

标签: haskell

我有

类型列表
PrimMonad m => m [Double]

为了计算平均值,我定义了以下函数:

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)

但是,我遇到了avg功能的问题。我收到以下错误:

    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.

我需要做什么才能定义这个简单的函数?

2 个答案:

答案 0 :(得分:7)

问题在于/想要Double这样的数字,而不是像m Double这样的monad中的数字。

天真的修复可能是

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

但这并不令人满意,因为它将运行monadic动作xs两次。

我宁愿使用像

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

我也会避免将xs命名为monadic动作,因为xs可以很容易地用于普通列表。然而,这是个人偏好的问题。

更好的是,我首先定义一个非monadic平均值:

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

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

由于avgM太短,我可能会省略其定义,并在需要时直接调用fmap avg

答案 1 :(得分:1)

您面临的错误是双重的

  1. 你试图划分两个monadic值
  2. 您在monadic值上使用fromIntegral
  3. 现在到解决部分 - 最简单的方法是使用do符号来规避整个问题

    为了简单起见,我从getting-average-of-calculated-list-haskell

    复制了一个avg函数
    {-# 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
    

    但这可能不是你想要它太专业化的东西。

    我们需要的最小的事情只是Functor - 所以fmap avg也解决了你的问题。

    如果你想保留你的功能 - 那么你需要<*> Applicatives来组合。

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