I was wondering if there was a legitimate usage for IORef in Haskell? More specifically I would be thankful if someone could address the following or point to an appropriate place to know more about this:
- Is using IORef considered a bad Haskell practice? If yes, why? More specifically how is it better or worse than an IO monad?
If one was looking to add state to a program, isn't state monad a better (purer) way to do it. If one was feeling more imperative couldn't he still use STM, and MVar, and still be better off?
Are there programming scenarios that are easily handled using IORefs rather than STM, MVar, or the pure IO?
I am reading a paper that uses IORef for the code snippets, and it has been difficult read for me owing to the negative perception I have towards IORef. Rather than wallow in my ignorance, I thought asking my fellow Haskellers for help might be a better idea.
First off, I think that for code snippets in a paper, using
IORef
is perfectly sensible, particularly if the paper isn't about best practices for mutable references or concurrency.IORef
is simple to understand, has straightfoward syntax and semantics (especially in a non-concurrent setting), and is a natural choice if you want the reader to concentrate on aspects of your examples other than theIORef
s. It's unfortunate that the author's approach has backfired for you -- just ignore theIORef
s and pay attention to what the rest of what the paper is saying.(If the paper was about best practices for mutable references or concurrency, perhaps it was written before better alternatives were available.)
Anyway, to your larger question, the main objections to using
IORef
would be:STRef RealWorld
, and the only thing it adds overSTRef
are some atomic operations. In non-concurrent code, there's no good reason not to useSTRef s
values in anST s
monad, since they're more flexible -- you can run them in pure code withrunST
or, if needed, in the IO monad withstToIO
.MVar
andSTM
that are much easier to work with thanIORef
s.So, to the extent that mutable state is "bad" and -- if you really need it -- better alternatives are available depending on whether you do or don't need concurrency, there's not much to recommend
IORef
.On the other hand, if you are already working on some non-concurrent code in the
IO
monad because you need to perform actual IO operations, and you genuinely need some pervasive mutable state that isn't easy to disentangle from the IO, then usingIORef
s seems legitimate.With respect to your more specific questions:
I guess it would be safe to say that using
IORef
is considered "bad practice" when a weaker tool would do the job. That weaker tool might be anSTRef s
, or better yet aState
monad or better yet a rewritten higher-order algorithm that doesn't need any state at all. BecauseIORef
combines IO with mutable references, it's kind of an imperative sledgehammer that's likely to lead to the most unidiomatic Haskell code possible, so it's best avoided unless it's "obviously" the right solution for a particular problem.The
State
monad is usually the preferred idiomatic way to add state to a program, but it provides the "illusion" of a mutable state by threading a sequence of immutable state values through the computation, and not all algorithms can be efficiently implemented this way. Where true mutable state is required, anSTRef
is usually the natural choice in a non-concurrent setting. Note that you probably wouldn't useMVar
orSTM
in a non-concurrent setting -- there's no reason to use them in this case, and they would force you into theIO
monad even if you didn't otherwise need it.Yes, there are programming scenarios where either
IORef
orSTRef
are preferable toState
,STM
,MVar
, or pureIO
(see below). There are few scenarios whereIORef
is obviously preferable toSTRef
, but -- as mentioned above -- if you're already in theIO
monad and have a need for true mutable state that's entangled with IO operations, thenIORef
probably has the edge overSTRef
in terms of slightly cleaner syntax.Some examples of cases where either
IORef
orSTRef
is a good approach:Data.Unique
in thebase
package uses anIORef
as a global counter for generating unique objects.base
library, the file handle internals make extensive use ofIORef
s for attaching buffers to handles. This is a good example of "already being in the IO monad with entangled IO operations".vector
package, then technically you're using mutable byte arrays rather thanSTRef
orIORef
, but it's still morally equivalent.equivalence
package usesSTRef
s for an efficient implementation of the union-join algorithm.IORef
orSTRef
values for the mutable variables is generally going to be most efficient.