Haskell - 每次迭代时返回不同值的函数

时间:2016-04-22 05:50:40

标签: haskell

说我有一个功能:

doesAThing :: Int -> ChangeState

出于这个问题的目的,ChangeState是什么并不是特别重要,只有这样才能将一个Int作为一个参数,并且它会无限地迭代。

我想做的是采取这样的功能:

addNum :: [Int] -> Int
addNum [n] = foldl (+) 0 ([n] ++ [n + 1])

并将其用于doAThing。目前,该功能工作正常,但它没有做我想做的事情 - 它总是返回相同的数字。我们的想法是,每次进行迭代迭代时,addNum都会获取其先前的输出,并将其用作addNum的参数。即:

第一次迭代:假设n设置为0. addNum返回1(0 + 0 + 1)并且使用它来修改ChangeState。

第二次迭代:现在addNum将1作为参数,因此它返回3(0 + 1 + 2)并且使用它来修改ChangeState。

第三次迭代:现在addNum将3作为参数,返回7(0 + 3 + 4)并且使用7来修改ChangeState。

道歉,如果这是一个非常愚蠢的问题,自我教学Haskell有时会很难。

3 个答案:

答案 0 :(得分:2)

如果你想改变一些东西,那意味着你需要改变状态。有两种选择。首先,您可以使当前状态成为函数的参数之一,以及新状态 - 结果的一部分。像addNum :: [Int] -> ([Int], Int)一样。有一个State monad可以帮助解决这个问题,给人一​​种实际存在某种可变状态的错觉。

其次,您可以将状态存储在IORef中,并使您的函数使用IO monad,例如addNum :: IORef [Int] -> IO Int。这样你就可以实际修改IORef中保存的状态。还有其他monad允许相同的东西,比如ST,如果你的可变状态只在本地使用,那就太好了,或STM,如果你的应用程序是高度并发的,这会有所帮助。

我强烈推荐第一个选项。在你绝对需要之前,不要使用IO(或其他必要的东西)。

答案 1 :(得分:1)

在Haskell中你想要的是不可能的,有充分的理由,见下文。

有一种方法可以迭代一个函数,但是,在下一次迭代中为它提供自己的输出。

以下是一个例子:

这会创建无限列表

E:\S-chat\server\node_modules\mongoose\node_modules\mongodb\lib\mongodb\connection\base.js:246
        throw message;
        ^

TypeError: Cannot read property 'length' of undefined
    at processResults (E:\S-chat\server\node_modules\mongoose\node_modules\mongodb\lib\mongodb\db.js:1581:31)
    at E:\S-chat\server\node_modules\mongoose\node_modules\mongodb\lib\mongodb\db.js:1619:20

Haskell是一种纯语言,这意味着函数只能访问其参数,常量,其他函数,而不能访问其他函数。特别是,函数无法访问隐藏状态。因此,使用相同的输入,Haskell函数将始终计算相同的输出。

答案 2 :(得分:1)

你遇到过这样一种情况,即熟悉Haskell的程序员可能会转向monad。特别是州monad ,其中有一些合理的在线教程:

我不太了解您尝试描述的“addNum”操作,我认为您尝试定义它时存在一些缺陷。例如,您的代码总是需要一个列表只包含一个元素这一事实表明不应该有列表或foldl - 只需将n作为参数并添加即可。

但是根据您的描述,我认为以下内容尽可能地近似。 (如果不对monad和州monad进行一点研究,这是不可理解的,但希望它能为你提供一个与其他材料一起使用的例子。)

import Control.Monad.State

-- Add the previous `addNum` result to the argument.
addNum :: Int -> State Int Int
addNum n = do
  -- Precondition: the previous call to `addNum` used `put`
  -- (a few lines below here) to record its result as the 
  -- implicit state for the `State` monad.
  previous <- get   
  let newResult = previous + n

  -- Here we fulfill the precondition above.
  put newResult
  return newResult

State monad的想法是你有getput个动作,以便执行get检索最近作为{{{1}的参数给出的值1}}。要实际使用put,您需要在调用addNum之类的函数的上下文中执行此操作,这会强制您指定第一个evalState :: State s a -> s -> a将看到的初始状态在第一次使用get时。

因此,例如,我们在这里使用addNum函数将列表traverse的连续元素上的调用链接到addNum。对每个列表元素的[1..10]每次调用都将addNum get的值newResult调用前一个元素。put0的{​​{1}}参数意味着第一个evalState调用addNum是0:

get

如果这感觉压倒一切,好吧,无论好坏,这就是Haskell最初的感受。坚持下去并慢慢积累到像这样的例子。