Where OCaml wins is that it does not hate mutation. This makes the code more impure but simpler. Sometimes just incrementing a mutable variable makes more sense than introducing a state monad!
Adding mutability to the mix via IORefs + Haskell's laziness by default means you don't _really_ know when a particular IORef mutation may really be executed.
In OCaml, evaluation is eager and you know when it will happen. Even when you introduce bangs and special Haskell language extensions, fully removing laziness from your code in Haskell is difficult.
TL;DR: Because of Haskell's laziness you are in some ways forced to be purely functional. OCaml allows you to embrace mutation in ways Haskell does not, in my view.
> But in a multi-core environment you will eventually want a guaranteed STM.
You actually may not always always want STMs. STMs are awesome but something more basic like locks may do the job. It can be more performant to use, say, a mutex. If STMs were superior on every parameter they would have taken over the world. They have not -- they are good in some scenarios and not so good in others...
Why would one prefer to fully remove laziness?
> TL;DR: Because of Haskell's laziness you are in some ways forced to be purely functional. OCaml allows you to embrace mutation in ways Haskell does not, in my view.
unless there's a strive to be more on the side of the Safe[1] language subset, Haskell can be told to embrace the same mutation property with a combination of `unsafePerformIO` and `seq`. People tend to avoid it because the language offers a tooling to achieve better mutation properties. Overall, my argument here is that OCaml's idiomatic approach to permissible mutations doesn't seem to be a competitive advantage when it comes to software development in general, but rather a flavour that imperative programmers prefer due to their familiarity with the concept.
> You actually may not always always want STMs.
Whenever there's a non-trivial retry policy, one should prefer to choose STM + queues over debugging adventures with Locks and Mutexes. Almost all of multithreaded software I can immediately think of have non-trivial retrying logic.
[1] https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/safe...
Far from an Ocaml/Haskell expert, but is it that laziness makes resource use less predictable?
[1] https://wiki.haskell.org/GHC_optimisations
[2] https://markkarpov.com/tutorial/ghc-optimization-and-fusion....
OCaml's variables are immutable. To use mutation, you have to do so explicitly with the `ref` type, e.g.:
let box = ref 0 in
print_endline ("box value: " ++ int_of_string !box);
box := !box + 1;
print_endline ("box value: " ++ int_of_string !box)
will print first `0` and then `1`. You cannot put a non-integer into the box because the box has type `int ref`, meaning only values of type `int` can be put into it with the `:=` operator.For example in normal Haskell the code that uses mutation and code that does not use mutation have different types. A function may have the type
f :: Int -> Int
but with mutation it can become f :: Int -> ST s Int
or f :: Int -> IO Int
or f :: Int -> STM Int
depending on the circumstances. That's not the case in OCaml. The kind of mutation in OCaml references is the most similar to Haskell's ST monad (not to be confused with State or StateT), but in Haskell the type tells us a mutation is happening inside the function call.https://medium.com/@asankhaya/reason-with-ocaml-b75697a6c00b
That said I don't know Haskell to compare!
Well I don't think you should have been the one embarrassed in that scenario.
I don’t think Ocaml had that backtick infix syntax for non-symbol function names though!