Had an idle thought this morning: Would it be safe to have a new primitive
disownSTRef :: STRef s a -> ST s (STRef s' a)
? Note that s'
is completely unconstrained. In particular, it could be RealWorld
, which would mean that you could now create an IORef
at toplevel without unsafePerformIO
, which would be nice.
The idea is that any STRef s a
must have been created within the invocation of runST
that we’re now in, and so disowning the ref can’t interfere with other state in the outside world.
Even more powerfully, we can imagine something like this:
data Purity
= Ref Type
| Pure
type family STRefish s a where
STRefish (Ref s) a = STRef s a
STRefish Pure a = a
disown :: t (Ref s) -> ST s (t s')
-- primitive which I think is safe and can be a no-op at runtime
data GraphNode s = Node { payload :: Int, neighbors :: STRefish s [GraphNode s] }
toplevelGraph :: GraphNode Pure
toplevelGraph = runST $ do
graph <- constructGraph
disown graph
And now we have a completely pure data structure constructed using mutability, but emancipated from any monad.
Could this possibly work and be safe?