If "we" already know these things are hard, why are "we" all
programming in languages with mutable state? That seems to indicate
that "we" are all idiots, or masochists.The same reason people still use C -- perceived efficiency, and 30+
years of history. Computers are just a big block of mutable state
that runs instructions, after all, so the first thing that comes to
mind is to program them without abstracting that away.
The reason we stick with this is that nobody wants to give up their
favorite language (mostly for perceived employment reasons, if you
don't know Java, you are unemployable or something), and "their
favorite language" doesn't support immutability well. Try writing
composible effectful computations in Java. Doesn't work. So you have
to do something else. (If you have effectful computations that know
about each other, you can put them in a class. If they don't know
about each other, you basically have to manage the state manually.)
I experimented with this recently in Perl, via MooseX::Data
(http://github.com/jrockway/MooseX-Data/). It ports various
category-theoretic type classes to Perl. (You could do this in any
language, although Java is not one of them unless you want to
copypaste the standard liftM-in-terms-of->>= code into every class
that implements "Monad", etc. Scala fixes this particular problem.)
Anyway, without special syntax, it ends up being a real mess. For
example, consider incrementing a global state variable by one:
use MooseX::Data::Function::State;
sub S() { MooseX::Data::Function::State:: } # save typing
my $inc = S->get->bind(
S->new(
function => sub {
my ($state) = @_;
S->mreturn($state+1);
},
)->bind( S->put ));
And printing it:
my $print = S->get->bind(
S->new(
function => sub {
my ($state) = @_;
say $state;
S->mreturn($state);
},
),
);
So now to inject 42 into the environment, increment it twice, and
print it:
my $result = $inc->bind($inc)->bind($print)->runState(42);
This prints 44 and returns [44, 44], as expected. (44 is the state,
44 is the unlifted return value of $print->apply(44).)
Anyway, this is an awful lot of code compared to the imperative
version:
sub inc { $_++ }
sub prnt { print }
$_ = 42;
inc;
print;
return $_
(The functional version composes better, though; you can write a
function that acts on "the" shared state, and you can compose these
arbitrarily. In Perl, you only get one $_
per program.
The tree-numbering example here is an excellent demonstration of
composablity:
http://www.haskell.org/ghc/docs/latest/html/libraries/mtl/Co...
Note how there is no mutable state here, just a bunch of functions
that pass (s,a) pairs to each other. And yet, you don't see that, you
"see" composable mutable state. In this case, we are composing
numberTree with itself recursively.)
To get back to my original point, with the right language,
immutability is as expressive as mutable state. The increment example
in Haskell:
import Control.Monad.State
type Step = StateT Int IO Int -- save typing
inc, prnt :: Step
inc = do
x <- get
let x' = x + 1
put x'
return x'
prnt = do
x <- get
liftIO.putStrLn $ show x
return x
main = runStateT (inc >> inc >> prnt) 42
-- or prnt << inc << inc
This prints:
44
(44,44)
It is worth pointing out that I combined the function calls with >>
instead of >>= so you can see that the values are being passed
implicitly (via get and put), rather than as arguments to the
individual functions, which would look like
(putStrLn.show) . (1+) . (1+) $ 42
Anyway, it's a function of languages. If everyone was using Haskell,
very few programs would be using mutable state. But since they are
using Java, and the only state encapsulation tool Java supports well
are classes, that's what everyone uses. Very few people care about
"doing it right", they care about having a working program. And while
class-based state encapsulation is inferior to immutability in many
ways, most developers don't know this, and don't care.