Closed noughtmare closed 1 year ago
safeStToIO
appears to be equivalent topure $! runST k
, which is more general -Applicative f => (forall s. ST s a) -> f a
.
As I explained way up thread, this is incorrect.
Is that due to the semantics of pure () >> runST (pure @IO <$> st)
or the optimization difficulty?
The semantics. forall s. ST s a
is closer to Solo a
than to a
. STtoIO
reveals that. runST
confuses matters by throwing away the distinction. I think if the system were being designed today, we'd likely have runST :: (forall s. ST s a) -> Solo a
.
That makes sense, thank you for clarifying!
@noughtmare could you please raise a draft MR, which we can vote on?
@noughtmare just a gentle reminder to make some progress. Otherwise I'll close the proposal as abandoned in two weeks.
Closing as abandoned.
I'd really like to get the safe version, with or without any deprecation. Do I need to open my own issue?
@treeowl would you like to take over the proposal? I think it's pretty much ready as is, except a missing MR.
Summary
The existence of the safe
stToIO
function enables one to useST
computations in multiple threads. This means we technically don't have the guarantee thatST
computations run in a single thread which most people expect and base their programs on. I propose address this by deprecatingstToIO
, introducing one safe but more restricted replacement and one function which does the same thing but is explicitly marked unsafe, and adding documentation about this issue.Example
We might think
ST
computations are single-threaded an thus do not need to use atomic operations (in fact,STRef
has no atomicmodify
operation), so we might write aprogramST
function as follows:(The
unsafeIOToST yield
is only to make it more likely that weird concurrent interleavings occur)But we can actually use this function in an unsafe way:
Compiling with
ghc -O2 T.hs -threaded
and running with./T +RTS -N2
sometimes gives me unexpected results:Click to expand full repro source code
```haskell import Control.Monad.ST import Control.Monad.ST.Unsafe import Control.Concurrent import Data.STRef programST :: STRef s Integer -> ST s () programST ref = do n <- readSTRef ref if n <= 0 then pure () else do unsafeIOToST yield writeSTRef ref $! n - 1 programST ref countdownST :: Integer -> IO Integer countdownST n = do ref <- stToIO (newSTRef n) forkIO $ stToIO (programST ref) stToIO (programST ref) s <- stToIO (readSTRef ref) pure s main :: IO () main = do putStrLn . show =<< countdownST 1000000 ```Proposed changes
I propose to introduce two new functions:
Additionally, I propose to deprecate (but not remove)
stToIO
suggesting users to use the newsafeSTToIO
orunsafeSTToIO
instead.I do not propose removing
stToIO
because that would break too many existing packages. Perhaps we can consider that at a later time, when most of the ecosystem has adapted to this change.Addtionally, documentation should be updated to explain the unsafety.
Impact
The results from this hackage search shows that there are 674 matches of the string
stToIO
in 95 packages. Some matches may be in comments, using the more complicated patternstToIO\s*[$(]|=\s*stToIO
in an attempt to exclude occurrences in comments yields 515 matches across 71 packages.Adding a deprecation warning lets package maintainers update their packages at their own pace, so I expect the migration to be a smooth process.
See also
https://gitlab.haskell.org/ghc/ghc/-/issues/22780 https://gitlab.haskell.org/ghc/ghc/-/issues/22764#note_473050