2 个使用 do 表示法的类似 Haskell 函数返回相同的结果,但一个被多次调用

Doo*_*oot 4 optimization recursion haskell

nextState :: IO Int -> IO Int -- 0 1 0 2 0 1 0
nextState stateIO = do
  value <- stateIO
  putStrLn $ "Current state: " ++ show value
  fmap (+1) stateIO

nextState' :: IO Int -> IO Int -- 0 1 2
nextState' stateIO = do
  value <- stateIO
  putStrLn $ "Current state: " ++ show value
  return $ value + 1

main :: IO ()
main = do
  let startStateIO = return 0 :: IO Int
  let states = iterate nextState' startStateIO -- Use nextState or nextState'
  stateInt <- states !! 3
  print stateInt -- 3 in both cases
Run Code Online (Sandbox Code Playgroud)

这个 Haskell 代码有 2 个函数,它们看起来都具有相同的行为。但是,打印调用显示它nextState被调用的次数比nextState'. 我有一个更大的项目,这是一个问题,我无法弄清楚如何转换该函数,以便它被调用的次数最少,因此我无法修复它。

为什么会发生这种情况以及如何在一个不太简单的示例中防止它发生?

请注意,fmap (+1)在我的实际项目中只是一个来自 的函数IO a -> IO a,而不是fmap (a -> a)- 整个事情都在 IO 方面工作,而不是修改内部使用的值(a->a)

lef*_*out 7

这个例子应该更容易理解,它是类似的:

twice :: IO () -> IO ()
twice act = do
   () <- act
   fmap id act -- like what you did in `nextState`

once :: IO () -> IO ()
once act = do
   () <- act
   return $ id ()  -- like what you did in `nextState'`
Run Code Online (Sandbox Code Playgroud)

...或更短

twice :: IO () -> IO ()
twice act = act >> act

once :: IO () -> IO ()
once act = act
Run Code Online (Sandbox Code Playgroud)

例如,

> twice (putStrLn "hello")
hello
hello
> once (putStrLn "hello")
hello
Run Code Online (Sandbox Code Playgroud)

迭代once不做任何事情,因为它只是身份。

> iterate once (putStrLn "hello") !! 4
hello
Run Code Online (Sandbox Code Playgroud)

迭代两次,但是...

Prelude> iterate twice (putStrLn "hello") !! 4
hello
hello
hello
hello
hello
hello
hello
hello
hello
hello
hello
hello
hello
hello
hello
hello
Run Code Online (Sandbox Code Playgroud)