I'm going to answer your second question first. There are actually many ways to handle mutable state in Haskell (and other FP languages). First of all, Haskell does support mutable state in IO, through IORef
and mvar
constructs. Using these will feel very familiar to programmers from imperative languages. here are also specialized versions such as STRef
and TMVar
, as well as mutable arrays, pointers, and various other mutable data. The biggest drawback is that these are generally only available within IO or a more specialized monad.
The most common way to simulate state in a functional language is explicitly passing state as a function argument and returned value. For example:
randomGen :: Seed -> (Int, Seed)
Here randomGen
takes a seed parameter and returns a new seed. Every time you call it, you need to keep track of the seed for the next iteration. This technique is always available for state passing, but it quickly gets tedious.
Probably the most common Haskell approach is to use a monad to encapsulate this state passing. We can replace randomGen
with this:
-- a Random monad is simply a Seed value as state
type Random a = State Seed a
randomGen2 :: Random Int
randomGen2 = do
seed <- get
let (x,seed') = randomGen seed
put seed'
return x
Now any functions which need a PSRG can run within the Random monad to request them as needed. You just need to provide an initial state and the computation.
runRandomComputation :: Random a -> Seed -> a
runRandomComputation = evalState
(note there are functions which considerably shorten the definition of randomGen2; I chose the most explicit version).
If your random computation also needs access to IO
, then you use the monad transformer version of State, StateT
.
Of special note is the ST
monad, which essentially provides a mechanism to encapsulate IO-specific mutations away from the rest of IO. The ST monad provides STRefs, which are a mutable reference to data, and also mutable arrays. Using ST, it's possible to define things like this:
randomList :: Seed -> [Int]
where [Int] is an infinite list of random numbers (it'll cycle eventually depending on your PSRG) from the starting seed you give it.
Finally, there's Functional Reactive Programming. Probably the current most prominent libraries for this are Yampa and Reactive, but the others are worth looking at also. There are several approaches to mutable state within the various implementations of FRP; from my slight use of them they often seem similar in concept to a signalling framework as in QT or Gtk+ (e.g. adding listeners for events).
Now, for the first question. For me, the biggest advantage is that mutable state is separated from other code at the type level. This means that code can't accidentally modify state unless it's explicitly mentioned in the type signature. It also gives very good control of read-only state versus mutable state (Reader monad vs. State monad). I find it very useful to structure my code in this way, and it's useful to be able to tell just from the type signature if a function could be mutating state unexpectedly.
I personally don't really have any reservations about using mutable state in Haskell. The biggest difficulty is that it can be tedious to add state to something that didn't need it previously, but the same thing would be tedious in other languages I've used for similar tasks (C#, Python).