I tried to provide a reasonable and balanced overview of these two languages, although my experience in both is significantly different. I hope this blog post can give a rough idea of both languages
I wanted to come up with a function to show the difference between guards and if-then-else. I now see that this is not the best example, as it can be written with pattern-matching in both cases, but I’ll gladly hear other suggestions!
Although I have only recently dabbled with OCaml, I def. agree with you that the features of ocaml are just easier to work with to be productive. I also find that the ecosystem of OCaml is much smaller but the packages strike a better abstraction level, IMO Dream is much easier to work with and test than most Haskell web frameworks I’ve dealt with.
There are also a lot of fundamental things that are so complex in haskell (lens for records, error handling and monad stacks) which you don’t need to touch if you use ocaml. That means as a consequence, the error message, while maybe less descriptive, become a lot easier to grok. The super fast compile times make the LSP and just working with the language in general sooooo much more ergonomic. Also polymorphic variants make error handling simple.
I will say though that my experience with OCaml is that you are more likely to deal with dependency hell and you’ll need more system level packages, which is not something I had to deal with as much in Haskell.
There are areas of opam that are very good with this and some that are not. I guess it really depends on the maintainer and maybe the domain of the deps.
One basic difference that I think adds up on Ocaml’s side is evaluation. Lazy evaluation is brilliant, elegant, fascinating and sometimes I miss it, but too many unpleasant surprises for the unwary.
And Ocaml is stable. Code I wrote several versions back, compiles today without any issues.
The parse_line example can be written a bit more pleasantly with Scanf.sscanf_opt:
let parse_line (line: string): int option =
Option.join (Scanf.sscanf_opt line "Status: %d | Result: %d"
(fun status result ->
if status = 0 && result mod 2 = 0 then
Some result
else
None))
The IDE situation in OCaml wasn’t this nice a few years ago. It’s a very recent thing.
There are some languages where, if you don’t use them for a couple of years and try to recreate a working environment again, you might actually fail if you’re not dedicated (like, if you’re just doing it for the fun of it)
The author did mention it in the checklist section. People sometimes use it.
First-class modules are really cool, I miss them in F# when I use it (I don’t get to use either often these days )
FYI, ez_toml is better than than otoml. For instance it keeps comments attached to expressions when parsing so that they can be reprinted. It also handles values than can overflow (int or float) better.
Nice post! I love both Haskell and OCaml, though my experience with Haskell is mostly limited to small experiments and toy programs. While my experience is very different from yours, I came to a similar conclusion: Haskell is more elegant on several planes, but actually “getting stuff done” tends to be more straightforward and obvious in OCaml. My feeling is that the extra verbosity in OCaml often also aids readability of the code, though it does make writing certain things more laborious at times.
An example of this would be module functors vs. type classes. While people don’t often speak about module functors in terms of high order polymorphism, they do actually provide a mechanism for abstracting over type constructors. However, because a new module is created when a functor is applied, it tends to be much more obvious at the call site which concrete types are involved, which is helpful for reading/debugging—though perhaps this is less of an issue for a seasoned Haskell developer?
Another issue with functors vs. type classes is that, since defining and/or adding operations to a functor is more laborious,* it can have the effect of limiting their use to when it is really necessary. I’m not trying to defend OCaml’s inelegance in this regard. It’s kind of a wart on the language, at least relative to Haskell. The irony is just that, since we tend to be more judicious in our use of functors, codebases overall tend to be more readable.
And code readability is important—especially since documentation in the OCaml ecosystem is, on average, horrible.
* Well, implementing type classes also has its unpleasantness in some cases. i.e. I have to implement Applicative and Functor in order to implement Monad, wherease the structural typing of OCaml modules means that this nonsense never happens. That’s not to say that type classes overall are less elegant.
Nice article.
Notice the Haskell and OCaml codes in “Sum of all numbers in a string” aren’t equivalent, in a way that IMO makes OCaml look a bit more convoluted. The Haskell equivalent should handle bad strings too, to be comparable.
main :: IO ()
main = do
putStrLn $ show $ strSum "100 -42 a"
Command exited with code 1.
Errors
Main: Prelude.read: no parse
# let () = print_int @@ str_sum "100 -42 a"
58
More generally, I think this code is not representative of real-life situations in the sense that real-life code usually doesn’t reside in the Prelude but in modules that you must import (as OCaml does with List. and String.). It would be nice to come up with an example that show this.