HPT’s via its module system is the big one. In contrast Haskell focuses on HKT’s.
HKT’s are Higher Kinded Types, this is what lets you thread a type through a program by using the type arguments. This is convenient to use but is a massive detriment to compilation time.
HPT’s, which are Higher Polymorphic Types, can do everything HKT’s can, and more, though it tends to be a little bit more verbose (though there are incoming PR’s to shorten it as well, though it’s often short enough to not worry about anyway).
In short though, OCaml’s modules are more like records, but can have a variety of interfaces defined for a given module (think of it kind of like Rust’s interface system or using Java/C# with a single class implementing many interfaces), you can define a module and ‘expose’ it via a variety of typed interfaces for the module. Just like those, in OCaml modules are first-class, you can ‘pack up’ an entire module into a variable and unpack and use it as you wish. You can even generate new module instances at run time, so you could have a function return a different result, or do something different entirely, as long as it follows the defined interface (or new one returned out), all entirely type safe.
Now going beyond the ‘interface’ style is that you can actually ‘do work’ on the modules at compile time, which could of course do stuff, even generate new modules with new types and interfaces and all, it would be like generating new interfaces at compile time based on other types in Java/C#, of which you cannot do.
And other smaller things that either Haskell does not have or is only accessible via extensions (on say GHC) are things like Row-Typing (OCaml objects), GADT’s, a unified string type (lacking that is hell on Haskell at times…), among far more, all built in. As well as OCaml has an entire ‘macro’ / pipeline-transforming-system called PPX’s that allow for new code constructs to be defined, kind of like Macro’s in Elixir except more powerful, however they are not as easy to write as Elixir macro’s. ^.^
In general:
- Haskell was designed by researchers to think about research problems, and it shows as it has 50 ways of doing anything with many inconsistencies.
- OCaml started from a research lab that was backed by businesses to get ‘real work done’ (very much like Erlang) and it even has a way to break it’s purity (exclusively via
Obj.magic
) if absolutely necessary (of which it never really is to be honest unless you are doing assembly-level optimizations), it was designed to be ‘useful’ and it’s feature-set reflects that by being easy to use, easy to write, easy to read, fast code that is near C in speed, the fastest optimizing compiler of any language, etc…