Haskell: 在 ST monad 中打印中间值

Haskell: printing intermediate values in ST monad

我正在尝试学习如何使用 ST Monad。我想知道是否可以在采取行动时打印值。例如,在 ST Monad:

中使用 Fibonacci 函数
fibST :: Integer -> Integer
fibST n
  | n < 2     = n
  | otherwise =
    runST $ do
      a <- newSTRef 0
      b <- newSTRef 1
      go n a b
    where
      go 0 a _ = readSTRef a
      go n a b = do
        a' <- readSTRef a
        b' <- readSTRef b
        writeSTRef a b'
        writeSTRef b (a' + b')
        -- print "SOME INFORMATION HERE" <---
        go (n - 1) a b

在这种情况下我需要做什么?

@Vikstapolis 建议改用 IORef

fibIO :: Integer -> IO Integer
fibIO n
  | n < 2     = pure n
  | otherwise =
    do
      a <- newIORef 0
      b <- newIORef 1
      go n a b
    where
      go 0 a _ = readIORef a
      go n a b = do
        a' <- readIORef a
        b' <- readIORef b
        writeIORef a b'
        writeIORef b (a' + b')
        print a' -- intermediate values
        go (n - 1) a b

TLDR: 不,您不能在 ST monad 中使用 print 或其他 IO-related 函数。在 IO monad 中使用 IORefs 来实现可变性和打印值的能力。

完整答案:
在 Haskell 中,每个值都是不可变的。这有助于保持其纯度。 Purity 也不允许打印或用户输入等副作用。
然而,不能接受输入或打印输出的程序是无用的。因此,Haskell 在 IO monad 中有 显式不纯函数 。通过 IORefs,您还可以在 IO monad 中具有可变性。

但有时,您需要使用可变性来提高函数的效率,并且可以保证函数是纯粹的或引用透明(即相同的输入将始终给出相同的输出)。这就是 ST monad 的目的。

ST monad 不允许任意的副作用,例如打印,因为那会破坏它的纯度。它只允许在其中进行内部突变,并且一旦您使用 runST 转义了 monad,它就会对程序的其余部分显示为纯计算。

因此,如果您确实需要打印值并进行突变,请在 IO monad 中使用 IORefs。对于具有内部突变的纯函数,使用 ST.

注意:可以使用 unsafePerformIO, and to ST with unsafeIOToST 将 IO 添加到纯函数。顾名思义,它们是不安全的,除非绝对必要(通常​​不是),否则应避免使用。