haskell出队的不变性

use*_*244 2 haskell

我有一个应用程序需要处理队列上的一些项目。有时脱下物品,有时穿上新物品。鉴于 haskell 数据结构的不变性,我对如何实现这一点感到困惑。这是我的实验代码

module Main where

import Data.Dequeue as D
import Data.Time
import Control.Concurrent (threadDelay)
import Control.Monad (forever)


data ScheduledCall = ScheduledCall {
        not_before :: UTCTime
    ,   apiParameters :: String
}

sleep :: Int -> IO ()
sleep n = threadDelay (n * 1000 * 1000)

main :: IO ()
main = do
    let workQ :: D.BankersDequeue ScheduledCall
        workQ = D.empty
    now <- getCurrentTime
    forever $ do
        let workQ' = D.pushBack workQ (ScheduledCall now "first")
        -- possibly do something with the items
        let len = length workQ'
        let workQ = workQ'
        putStrLn $ "Length " ++ (show len)
        sleep 5
Run Code Online (Sandbox Code Playgroud)

使用这段代码,我希望看到出队在每次迭代中增加 1,但事实并非如此。这意味着结构保持不变。

如何通过不同的迭代使用出队?

chi*_*chi 5

您可以使用递归。

main = do
   now <- getCurrentTime
   let loop workQ = do
          let workQ' = D.pushBack workQ (ScheduledCall now "first")
          -- possibly do something with the items
          let len = length workQ'
          putStrLn $ "Length " ++ (show len)
          sleep 5
          loop workQ'   -- recurse with the new queue
   loop D.empty  -- start the loop with an initially empty dqueue
Run Code Online (Sandbox Code Playgroud)

有更先进(和复杂)的技术来建模可变性,例如使用IORefs 或ST/ Statemonad,但对于简单的情况,基本的递归定义就足够了。


请注意,您不能像尝试那样“重新分配”:

let workQ = workQ'
putStrLn $ "Length " ++ (show len)
sleep 5
Run Code Online (Sandbox Code Playgroud)

这定义了一个变量workQ,该变量与上面定义的同名的其他变量完全无关。新变量的作用域仅是以下几行,其中未使用它,因此它是一个无用的定义。

您可以将这种重新定义视为 C++ 中发生的情况:

main = do
   now <- getCurrentTime
   let loop workQ = do
          let workQ' = D.pushBack workQ (ScheduledCall now "first")
          -- possibly do something with the items
          let len = length workQ'
          putStrLn $ "Length " ++ (show len)
          sleep 5
          loop workQ'   -- recurse with the new queue
   loop D.empty  -- start the loop with an initially empty dqueue
Run Code Online (Sandbox Code Playgroud)

定义一个与外部作用域中定义的另一个变量同名的变量称为“遮蔽”。GHC 会对此发出警告(如果您启用警告),因为这通常是一个错误。

  • 作为一般规则,尾递归在大多数 Haskell 程序中既不需要也不可取。事实上,在这个特定的例子中,“loop”不是尾递归,尽管这个事实被“do”符号掩盖了。尽管如此,它永远不会导致堆栈溢出。在进行 Haskell 编程时,尝试忘记在其他语言中学到的有关递归的所有内容是个好主意。假装这种简单的、无界的递归“正常工作”,直到您真正编写出一个溢出堆栈的程序(或者更有可能的是,吞噬了千兆字节的内存)。 (2认同)