Pure functional programming languages do not allow mutable data, but some computations are more naturally/intuitively expressed in an imperative way — or an imperative version of an algorithm may be more efficient. I am aware that most functional languages are not pure, and let you assign/reassign variables and do imperative things but generally discourage it.
My question is, why not allow local state to be manipulated in local variables, but require that functions can only access their own locals and global constants (or just constants defined in an outer scope)? That way, all functions maintain referential transparency (they always give the same return value given the same arguments), but within a function, a computation can be expressed in imperative terms (like, say, a while loop).
IO and such could still be accomplished in the normal functional ways – through monads or passing around a “world” or “universe” token.
The short answer is: there are systems to allow what you want. For example, you can do it using the
STmonad in Haskell (as referenced in the comments).The
STmonad approach is from Haskell’sControl.Monad.ST. Code written in theSTmonad can use references (STRef) where convenient. The nice part is that you can even use the results of theSTmonad in pure code, as it is essentially self-contained (this is basically what you were wanting in the question).The proof of this self-contained property is done through the type-system. The
STmonad carries a state-thread parameter, usually denoted with a type-variables. When you have such a computation you’ll have monadic result, with a type like:To actually turn this into a pure result, you have to use
You can read this type like: give me a computation where the
stype parameter doesn’t matter, and I can give you back the result of the computation, without theSTbaggage. This basically keeps the mutableSTvariables from escaping, as they would carry theswith them, which would be caught by the type system.This can be used to good effect on pure structures that are implemented with underlying mutable structures (like the vector package). One can cast off the immutability for a limited time to do something that mutates the underlying array in place. For example, one could combine the immutable
Vectorwith an impure algorithms package to keep the most of the performance characteristics of the in place sorting algorithms and still get purity.In this case it would look something like:
The
thawandfreezefunctions are linear-time copying, but this won’t disrupt the overall O(n lg n) running time. You can even useunsafeFreezeto avoid another linear traversal, as the mutable vector isn’t used again.