In addition to the mutable records mentioned in the post, arrays and references are also mutable in OCaml. Aside from objects, I consider this impurity to be the biggest difference in practice. Mutability allows simple OCaml solutions to problems that would otherwise demand monadic gymnastics in Haskell.
FWIW, I've also never been able to design a large project in Haskell (a la darcs), whereas I could write fairly large projects in OCaml without much awkwardness. I suspect the ability to "cheat" with mutability outweighs in practical terms the theoretical elegance and enjoyment of programming in Haskell.
"Cheating mutability" is trivial in Haskell. You import Data.IORef or Data.STRef and do your thing. (unsafePerformIO is just runST for "real world" actions.)
You'll find, though, that it's very easy to make a sequence of mutations become one pure operation, and the ST machinery lets you keep that sequence encapsulated. This is how building a vector works. In a new state thread, you allocate the vector. You run a pure function to generate the nth element. You bit-bang that into memory. When you're at the end of the vector, you freeze the vector into an immutable vector and return it. The final signature is "Length -> (Index -> Value) -> Vector of Values". Totally pure to the outside world, mutable internally. And the type system keeps the mutation part safe!
So while the first approach you try with Haskell should be actual pure code, you still have the tools you need to be as impure as you need to be. And you can still keep your program type safe, if you want.
It's most of the way there, but not quite. For example, one frequently wants to do a hybrid approach to mutability where construction of the data structure is destructive, but then further manipulation is persistent. ST-backed arrays will get you part of the way there, but there's no easy way to efficiently unsafely freeze (the unsafe coercion from mutable to immutable) recursive mutable structures into persistent structures. You have to very carefully make sure the internal representations match up, and it's tricky enough that it's not done very often. I guess my real point is there is a cognitive cost for respecting the fine distinctions that Haskell makes you think about, which discourages certain styles of programming, but is good for the mind. :-)
You can cheat in haskell as well. You can store the mutable variable in an mvar and then use unsafePerformIO (modifyMVar action) . It's uglier, but on the other hand the unsafeness of it jumps out at you.
6 comments
[ 3.8 ms ] story [ 24.0 ms ] threadFWIW, I've also never been able to design a large project in Haskell (a la darcs), whereas I could write fairly large projects in OCaml without much awkwardness. I suspect the ability to "cheat" with mutability outweighs in practical terms the theoretical elegance and enjoyment of programming in Haskell.
You'll find, though, that it's very easy to make a sequence of mutations become one pure operation, and the ST machinery lets you keep that sequence encapsulated. This is how building a vector works. In a new state thread, you allocate the vector. You run a pure function to generate the nth element. You bit-bang that into memory. When you're at the end of the vector, you freeze the vector into an immutable vector and return it. The final signature is "Length -> (Index -> Value) -> Vector of Values". Totally pure to the outside world, mutable internally. And the type system keeps the mutation part safe!
So while the first approach you try with Haskell should be actual pure code, you still have the tools you need to be as impure as you need to be. And you can still keep your program type safe, if you want.
If you want to write C in Haskell: