If you haven't tried haskell, you probably should. Its type system is much cleaner (and more powerful, if you want) than ocaml's, and many details of Rust are derived from Haskell.
If you haven't tried haskell, you probably should. Its type system is much cleaner (and more powerful, if you want) than ocaml's, and many details of Rust are derived from Haskell.
I'm very conscious of the existence of pareto tradeoffs; I am asserting that, in this case, there is essentially no tradeoff to be made. Haskell is equal or better (sometimes significantly so) in almost every relevant domain. For domains in which ocaml is a better choice, it is not the globally best choice (i.e. both ocaml and haskell are bad in those domains).
I think this assertion is false, but it would take a lot of careful evaluation to prove it one way or the other. The most salient counterexample that comes to mind is modularity and namespacing.
Of course it's possible I'm wrong, and Haskell is objectively superior in every regard. But the OCaml language and ecosystem has [a decades long track record of stable and evident success stories](https://ocaml.org/industrial-users) and I find it well suited for the kinds of problems I like to tackle. I also like the prevailing vibe in the (still) small community.
This exchange reminds of a difference I've observed in the cultural tendencies in the OCaml and Haskell ecosystems: in my experience, OCamlers tend to not be very invested in arguing for the supremacy of OCaml.
This is what I was doing for a number of years.
> The most salient counterexample that comes to mind is modularity and namespacing.
Polymorphic variants and ocaml namespacing are nice, granted. These are the two ocaml features I've ever missed while using haskell. Minor details overall though. Namespacing is not as useful with typeclasses and PVs have typing problems.
> in my experience, OCamlers tend to not be very invested in arguing for the supremacy of OCaml
Yeah, this is mostly because it's not as much of a marginal improvement, so it doesn't have as many people interested in shilling it.
In chp 7 on understanding IO, there's also another great section that explains how IO evaluation is often confused. I share an example from the book that reads, writes, and prints files.
Memory intensive func because it attempts to read _all_ files at once because the execution of reading and writing actually only happens when the print fn is called (putStrLn files). makeAndReadFile is actually doing both reading and writing - a normal task in all other languages.
slow =
let files = mapM makeAndReadFile [1..500] :: IO [ String ]
in files >>= (putStrLn . show)
Efficent version. Here we force reads and writes to actually occur per file instead of waiting til the print. Seems like a pretty easy step to misunderstand.
safe :: IO () safe =
foldl ( \ io id -> io >> makeAndShow id ) (return ()) [1..500]* it has never been an issue for me
* I don't think the performance implications are nearly as hard to understand as often implied
* in the extreme you can just enable the STRICT language pragma in your project and forget about it :)
Really, lazy IO is a much larger source of problems, and even there, after a week or two writing IO people just learn to write code that doesn't break due to it.
I know few cases where OCaml require less annotations than Haskell, I would expect the reverse to be true.
And maps and sets are not the main use case for polymorphism in OCaml. Even taking in account that it sounds like you are talking about functors, maps and sets are still not the only instance of functors in OCaml.
A) I am fairly confident this is not actually true in a technical sense, although it's been several years since I've thought about it
B) In any case, in the (many) instances where you (by rule or convention) need to put a type signature on your function (e.g. because it's a top-level function, one that you export, etc.), it is a pain in the ass to make it polymorphic in ocaml compared to in haskell.
> Even taking in account that it sounds like you are talking about functors
No, I'm not, although ocaml people tend to think exclusively in "functors" (badly named, in my opinion - conflicts with the more common category-theoretic definition) because the experience of using actually parametric functions is so bad that they almost never do it.
OCaml typing is principal in all situation where type inference is decidable. The syntax for annotation for polymorphic functions is isomorphic between OCaml and Haskell.
If I take a random module in the standard library, let's say Array, 95% of the functions in this module are parametric polymorphic. I am thus genuinely puzzled by your statement that "OCaml programmers almost never write polymorphic functions".
> The syntax for annotation for polymorphic functions is isomorphic between OCaml and Haskell.
Sorry, but total bullshit. I had to use these pieces of shit all the time. https://v2.ocaml.org/manual/locallyabstract.html
> If I take a random module in the standard library, let's say Array
As I specifically called out, all the core data structures are polymorphic. (Let's not talk about the float array hack; is that still around?)
It's so annoying that pretty much every other library is monomorphic or functorized.
> I am thus genuinely puzzled by your statement that "OCaml programmers almost never write polymorphic functions".
Not sure what to tell you man. I spent 4 years reading & writing ocaml and 95% of the code that would have been polymorphic in haskell (because it would be easy/free) was either monomorphic or (multiple layers of) functors. Many/most of the devs I worked with (great devs with years of OCaml experience) didn't even know what LATs were, let alone used them, which means they were almost certainly not writing polymorphic code which "did anything" with the type. The only polymorphic code that you can write in ocaml without such things are functorial (in the categorical sense, not the ocaml sense). Arrays, map values, that's about it.
Ok, locally abstract types are bit weird and historical quirk due to the pre-existing use of type variables as unification type variables. But first, they only matter with GADTs and local modules (a notion that doesn't exist in Haskell). And the good syntax for polymorphic function with GADTs is `type a. a monoid -> a` which requires just one explicit quantification compared to the Haskell variant. So no, Haskell and OCaml type annotations are isomorphic.
> As I specifically called out, all the core data structures are polymorphic.
I took a totally random module from the standard library! Let me another random module after rolling a dice, Atomic. Here only 66% of the functions are polymorphics. Or do you want me to go to another library? Ok, let's go for container, and let's select another random module CCPair: 100% of the functions are polymorphic. Honestly, I struggle to understand how it is possible to conclude that every OCaml library is monomorphic.
What do you mean by LATs? The Haskell wiki doesn't seem to know that term.
> The only polymorphic code that you can write in ocaml without such things are functorial
I am sorry to ask but are you confusing bounded polymorphism with parametric polymorphism?
Local modules (assuming this means e.g. a module passed in as a function argument, constrained to have one of its types match the type of another argument) are how you write non-trivial polymorphic code.
> Atomic ... CCPair
More functors (in haskell terminology) - code that doesn't do anything interesting with the type parameter.
> What do you mean by LAT
Locally abstract type
> I am sorry to ask but are you confusing bounded polymorphism with parametric polymorphism?
Bounded polymorphism is an application of parametric polymorphism. Not sure what you are asking here. Possibly this will clarify my line of thought: I consider parametrically polymorphic code without any bounds to be "uninteresting"/"trivial", because either the code must treat the polymorphic type completely opaquely (leading to trivial functorial [in the haskell sense] operations like implementing fmap), or requiring you to explicitly pass in all supported operations on the instantiated type (basically requiring you to implement bounding by hand).
I think this may be a blub paradox thing, where the set of useful applications of PP is much more restricted in ocaml, to the point where ocamlers do not even consider what they are missing. A haskell or rust programmer would likely run into these semantic blocks quickly upon trying ocaml.
I remember one annoyance I had with ocaml was the effective inability to use point-free style. I can't remember what limitation was behind this; do you know off the top of your head?
Edit: it's the "value restriction", more annoying bullshit I forgot about! Huge limitation for polymorphic ocaml code.
And in fact OCaml does have BP, as was explained earlier, just with a heavier syntax.
> effective inability to use point-free style
OK but point-free style is not an end in itself, it's just a means to an end (clean code), and there's certainly no universal agreement that it's the best means...explicit style is almost always easier to understand for a wide assortment of people and doesn't require mental backflips trying to figure out the weird heiroglyphics of '(.) (.) (.)' etc. etc.
> Huge limitation for polymorphic ocaml code.
Yeah I can see how writing e.g.
let f x = g [] x
As opposed to let f = g []
Is such a deal-breaker. /sOk, I'm glad we agree on that.
> I can see how writing
Eta reduction is one example, but a great deal of code is cleanly expressed as e.g.
vecLen = sum . map (^2)
And ocaml's type system is not capable of handling this gracefully. Very unfortunate! let vec_len vec = vec |> map (fun x -> x ** 2.) |> sum
Or even let square x = x ** 2.
let vec_len vec = vec |> map square |> sum
Honestly, if code like this is the biggest nitpick, I have a hard time taking the argument seriously.