【问题标题】:Haskell read n numbers from user and return their sumHaskell 从用户那里读取 n 个数字并返回它们的总和
【发布时间】:2018-04-22 10:28:39
【问题描述】:

我正在尝试编写一个函数isums,它从用户那里读取n 数字并返回它们的总和。此外,在每个数字之后,都会打印该数字的总和。到目前为止我有这个:

isums :: Int -> IO Int
isums n = do
    num <- readLn
    putStrLn (show (num + sum))
    sum <- isums (n - 1)
    return (num + sum)

我也没有使用 IORef。

【问题讨论】:

  • 使用replicateM 的解决方案更可取,而递归(如其他答案)是“函数式编程的跳转”。所以我建议用replicateM将“解决方案”标记切换到答案。
  • replicateM 解决方案没有在每个数字后打印总和,因此它不是此特定功能的解决方案。
  • 将显式递归等同于使用 goto 有点极端,但我喜欢这个想法。 :)
  • @Aelin 哦,错过了,抱歉。尽管如此,关于打印部分和的部分与replicateM-one 是正交的。要获得这些,您可以将答案中的sum 替换为scanl (+) 0
  • @chepner 这个主意不是我的,是 Erik Meijer 的。

标签: haskell io sum


【解决方案1】:

这可能是使用辅助函数最容易表达的,因为在每次输入后打印部分和的额外要求会增加一些额外的混乱:

isums :: Int -> IO Int
isums n = helper n 0
  where
    helper 0 acc = return acc
    helper m acc = do
        x <- readLn
        let x' = acc + x
        print x'
        helper (m - 1) x'

您所做的有点像折叠(查看foldM),只是不是遍历列表,而是从 IO 获取“要折叠”的值。如果我们有一个函数:

accM :: Monad m => (a -> m a) -> m a -> Int -> m a
accM f acc 0 = acc
accM f acc n = accM f (acc >>= f) (n - 1)

那么我们可以这样写:

isums :: Int -> IO Int
isums n = accM helper (return 0) n
  where
    helper acc = do
        x <- readLn
        let x' = acc + x
        print x'
        return x'

这更好一点(并且更可重用),因为它可以让我们将一般行为 (accM) 与特定行为 (helper) 分开。

【讨论】:

    【解决方案2】:

    这里还有一个解决方案:它建立在@Willem 的最后一个版本的基础上,但不是使用() 的列表(这有点令人印象深刻)作为循环的燃料(foldM ) 它应用一系列操作来读取值。

    import Control.Monad
    
    isums n = foldM go 0 (replicate n readLn) 
      where
        go s a = do 
          x <- a
          let s' = s + x 
          print s'
          return s'
    

    这里replicate n readLn 创建一个动作列表,每个动作读取一个整数。在循环期间通过foldM 调用go 之前,不会评估这些操作。事实上,我们可以在不执行实际读取的情况下创建这样一个列表,这源于 Haskell 的懒惰。

    【讨论】:

    • 懒惰 IO 操作列表的好用 - 我希望这样的事情是可能的,但我还没有来得及测试它。
    【解决方案3】:

    您可以为此使用replicateM :: Applicative m =&gt; Int -&gt; m a -&gt; m [a]

    import Control.Monad(replicateM)
    
    isums :: (Read n, Num n) => Int -> IO n
    isums n = do
        numbers <- replicateM n readLn
        return (sum numbers)
    

    所以这里我们重复readLn给定的次数,然后我们返回numbers列表中的sum

    使用fmap 可以实现等效:

    import Control.Monad(replicateM)
    
    isums :: (Read n, Num n) => Int -> IO n
    isums n = fmap sum (replicateM n readLn)
    

    甚至pointfree(和pointless):

    import Control.Monad(replicateM)
    
    isums :: (Read n, Num n) => Int -> IO n
    isums = fmap sum . flip replicateM readLn
    

    我们还可以使用scanl 生成部分和的列表:

    import Control.Monad(replicateM)
    
    isums :: (Read n, Num n) => Int -> IO [n]
    isums = fmap (scanl (+) 0) . flip replicateM readLn
    

    然后再处理列表,或者如果我们需要打印这些并返回最后一个,我们可以在该列表上执行mapM,例如:

    import Control.Monad(replicateM)
    
    isums :: (Read n, Num n) => Int -> IO ()
    isums n = fmap (scanl (+) 0) (replicateM n readLn) >>= mapM_ print
    

    或者如果我们需要打印部分总和:

    isums :: (Read n, Num n, Show n) => Int -> IO n
    isums n = foldM f 0 (replicate n ())
        where f a _ = readLn >>= \b -> let c = a + b in print c >> return c
    

    【讨论】:

    • 他们说答案中没有解决关于打印部分总和的部分。也许,你可以更新它,所以我们可以说服 OP 使用库函数而不是简单的递归。
    猜你喜欢
    • 2014-03-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-05
    • 2010-10-05
    相关资源
    最近更新 更多