I often encounter the following statements / arguments:
- Pure functional programming languages do not allow side effects (and are therefore of little use in practice because any useful program does have side effects, e.g. when it interacts with the external world).
- Pure functional programming languages do not allow to write a program that maintains state (which makes programming very awkward because in many application you do need state).
I am not an expert in functional languages but here is what I have understood about these topics until now.
Regarding point 1, you can interact with the environment in purely functional languages but you have to explicitly mark the code (functions) that introduces side effects (e.g. in Haskell by means of monadic types). Also, as far as I know computing by side effects (destructively updating data) should also be possible (using monadic types?) even though it is not the preferred way of working.
Regarding point 2, as far as I know you can represent state by threading values through several computation steps (in Haskell, again, using monadic types) but I have no practical experience doing this and my understanding is rather vague.
So, are the two statements above correct in any sense or are they just misconceptions about purely functional languages? If they are misconceptions, how did they come about?
Could you write a (possibly small) code snippet illustrating the Haskell idiomatic way to (1) implement side effects and (2) implement a computation with state?
Best Answer
For the purposes of this answer I define "purely functional language" to mean a functional language in which functions are referentially transparent, i.e. calling the same function multiple times with the same arguments will always produce the same results. This is, I believe, the usual definition of a purely functional language.
The easiest way to achieve referential transparency would indeed be to disallow side effects and there are indeed languages in which that is the case (mostly domain specific ones). However it is certainly not the only way and most general purpose purely functional languages (Haskell, Clean, ...) do allow side effect.
Also saying that a programming language without side effects is little use in practice isn't really fair I think - certainly not for domain specific languages, but even for general purpose languages, I'd imagine a language can be quite useful without providing side effects. Maybe not for console applications, but I think GUI applications can be nicely implemented without side-effects in, say, the functional reactive paradigm.
That's a bit over simplifying it. Just having a system where side-effecting functions need to be marked as such (similar to const-correctness in C++, but with general side-effects) is not enough to ensure referential transparency. You need to ensure that a program can never call a function multiple times with the same arguments and get different results. You could either do that by making things like
readLine
be something that's not a function (that's what Haskell does with the IO monad) or you could make it impossible to call side-effecting functions multiple times with the same argument (that's what Clean does). In the latter case the compiler would ensure that every time you call a side-effecting function, you do so with a fresh argument, and it would reject any program where you pass the same argument to a side-effecting function twice.Again, a purely functional language might very well disallow mutable state, but it's certainly possible to be pure and still have mutable state, if you implement it in the same way as I described with side-effects above. Really mutable state is just another form of side-effects.
That said, functional programming languages definitely do discourage mutable state - pure ones especially so. And I don't think that that makes programming awkward - quite the opposite. Sometimes (but not all that often) mutable state can't be avoided without losing performance or clarity (which is why languages like Haskell do have facilities for mutable state), but most often it can.
I think many people simply read "a function must produce the same result when called with the same arguments" and conclude from that that it's not possible to implement something like
readLine
or code that maintains mutable state. So they're simply not aware of the "cheats" that purely functional languages can use to introduce these things without breaking referential transparency.Also mutable state is heavily discourages in functional languages, so it isn't all that much of a leap to assume it's not allowed at all in purely functional ones.
Here's an application in Pseudo-Haskell that asks the user for a name and greets him. Pseudo-Haskell is a language that I just invented, which has Haskell's IO system, but uses more conventional syntax, more descriptive function names and has no
do
-notation (as that would just distract from how exactly the IO monad works):The clue here is that
readLine
is a value of typeIO<String>
andcomposeMonad
is a function that takes an argument of typeIO<T>
(for some typeT
) and another argument that is a function which takes an argument of typeT
and returns a value of typeIO<U>
(for some typeU
).print
is a function that takes a string and returns a value of typeIO<void>
.A value of type
IO<A>
is a value that "encodes" a given action that produces a value of typeA
.composeMonad(m, f)
produces a newIO
value that encodes the action ofm
followed by the action off(x)
, wherex
is the value produces by performing the action ofm
.Mutable state would look like this:
Here
mutableVariable
is a function that takes value of any typeT
and produces aMutableVariable<T>
. The functiongetValue
takesMutableVariable
and returns anIO<T>
that produces its current value.setValue
takes aMutableVariable<T>
and aT
and returns anIO<void>
that sets the value.composeVoidMonad
is the same ascomposeMonad
except that the first argument is anIO
that does not produce a sensible value and the second argument is another monad, not a function that returns a monad.In Haskell there's some syntactic sugar, that makes this whole ordeal less painful, but it's still obvious that mutable state is something that language doesn't really want you to do.