Languages like C# encourage use of the Dispose pattern to deal with optional early disposal.
E.g., the finaliser will call the protected Dispose method, which must immediately exit if it had been called before.
It is good practice to call Dispose in a using
statement
using(var socket = new Socket())
{
...
}
Which (similar to Java’s resourced try(...) { }
blocks) desugars to
{
var socket = new Socket();
try
{
...
}
finally
{
socket.Dispose();
}
}
In Haskell, we’d use bracket
to do the same.
The appeal of the Dispose pattern is that it works even if the programmer forgot or can’t use the using
block (perhaps its lifetime is non-lexically scoped) while it centralises the cleanup logic in one place.
Perhaps it makes sense to have a library like
class Disposable args d where
create :: args -> IO d
dispose :: d -> IO ()
wrap :: (d -> IO ()) -> d -> IO ()
wrap = ... -- implement Dispose pattern
scoped :: Disposable d => args -> (d -> IO r) -> IO r
scoped args run = bracket (create args) (wrap dispose) run
unscoped :: Disposable d => args -> IO d
unscoped args = mask $ \restore ->
d <- create args
... add finaliser for `wrap dispose d` ...
return d
Note that it is always possible to call dispose
on a d
in a scoped
block or on a d
returned by unscoped
. It is the job of wrap
to implemnt the Dispose pattern (e.g., needs a Bool
tracking whether disposal happened already. I just realised that it would need to return IO (d -> IO ())
, but I hope you get the idea). Perhaps that’s a useful library; at any rate I haven’t needed it so far.
Do also note that this makes me aware of a potential issue of allocateResource
in the OP: It doesn’t mask exceptions. If allocateResource
resource where to be canceled after Internal.allocateResource
but before addFinalizer
, you’d get a resource leak.
So perhaps such a library would pull its weight.