4

I am trying to learn how to use the ST Monad. I wonder if it is possible to print values as actions are taken. For example with a Fibonacci function in the ST Monad:

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

What would I have to do in this case?

@Vikstapolis suggested to use IORef instead

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
matt
  • 1,817
  • 14
  • 35
  • 4
    If for debugging, use `trace`. If not for debugging, it shouldn't be `ST` – Fyodor Soikin Feb 04 '22 at 02:15
  • The type `Integer -> Integer` promises that no I/O will occur. GHC enforces that by preventing prints. If it's for debugging purposes only, `trace` provides an exception top that. – chi Feb 04 '22 at 09:03

1 Answers1

6

TLDR: No, you cannot use print or other IO-related functions in the ST monad. Use IORefs in the IO monad for both mutability and the ability to print values.

Full answer:
In Haskell, every value is immutable. This helps preserve its purity. Purity also does not allow side effects such as printing or user input.
A program which cannot take input or print output, however, is useless. So, Haskell has explicit impure functions within the IO monad. By means of IORefs, you can also have mutability within the IO monad.

But sometimes, you need to use mutability to increase the efficiency of a function, and can guarantee that the function is pure or referentially transparent (i. e. The same input will always give the same output). This is the purpose of the ST monad.

The ST monad doesn’t allow arbitrary side effects such as printing, since that would destroy its purity. It only allows interior mutation within it, and once you escape the monad using runST, it appears as a pure computation to the rest of the program.

So, if you really need to print values and have mutation, use IORefs in the IO monad. For pure functions with interior mutation, use ST.

Note: it is possible to add IO to pure functions with unsafePerformIO, and to ST with unsafeIOToST. As the name indicates, they are unsafe and should be avoided unless absolutely necessary (which they usually aren’t).

Vikstapolis
  • 744
  • 3
  • 13
  • Thanks for the great answer. Do you have the time to write out an example? – matt Feb 04 '22 at 10:09
  • see my attempt in the edit – matt Feb 04 '22 at 11:00
  • @matt Your edit looks good, I don't have time to try it out right now but there's really only a couple of changes when converting from `ST` to `IO`: `readSTRef` and `writeSTRef` become `readIORef` and `writeIORef`, and you can't escape the monad using any equivalent to `runST` so the function needs to return an `IO something`. – Vikstapolis Feb 04 '22 at 11:27