[HN Gopher] OCaml as my primary language
       ___________________________________________________________________
        
       OCaml as my primary language
        
       Author : nukifw
       Score  : 186 points
       Date   : 2025-08-13 18:05 UTC (4 hours ago)
        
 (HTM) web link (xvw.lol)
 (TXT) w3m dump (xvw.lol)
        
       | moi2388 wrote:
       | If I wanted to program in OCaml, id program in F# instead
        
         | nukifw wrote:
         | Hi! Thank you for your interest (and for potentially reading
         | this).
         | 
         | Yes, F# is a very nice language, however, it seems to me that I
         | am making a somewhat forced comparison between OCaml and F# in
         | the following section: https://xvw.lol/en/articles/why-
         | ocaml.html#ocaml-and-f
        
           | Smaug123 wrote:
           | You can hack up GADTs in F# - for example, https://github.com
           | /Smaug123/WoofWare.Incremental/blob/9b8181... (which uses
           | https://github.com/G-Research/TypeEquality ). (The other
           | missing features I agree _are_ missing.)
        
             | nukifw wrote:
             | Yes, the trick is expanded here: https://libres.uncg.edu/ir
             | /asu/f/Johann_Patricia_2008_Founda... (if you have `Eq a b
             | = Refl : a a eq` you should be able to encode every useful
             | GADTs. But having a compiler support is nice for specifics
             | reason like being able to "try" to detect unreachable cases
             | in match branches for examples.
        
             | debugnik wrote:
             | I've used equality witnesses in F# before, they kinda work
             | but can't match proper GADTs. First you'll need identity
             | conversion methods on the witness, because patterns can't
             | introduce type equalities, then you'll realise you can't
             | refute unreachable branches for the same reason, so you
             | still need to use exceptions.
        
           | moi2388 wrote:
           | Thanks, that was an interesting read for sure!
        
         | jimbob45 wrote:
         | It's always weird when Microsoft pulls an "embrace, extend,
         | extinguish" but the "extinguish" part happens without their
         | involvement or desire and then we're all stuck wondering how
         | Microsoft got left holding the bag.
        
       | shortrounddev2 wrote:
       | OCaml is a great language without great tooling. Desperately
       | needs a good LSP implementation to run breakpoints and other
       | debugging tools on VSCode or other LSP-aware IDEs. I know there
       | ARE tools available but there isn't great support for them and
       | they don't work well
        
         | nukifw wrote:
         | Indeed, efforts should be made in terms of DAP
         | (https://microsoft.github.io/debug-adapter-protocol//),
         | extending the following experimentation:
         | https://lambdafoo.com/posts/2024-03-25-ocaml-debugging-
         | with-.... However, I find the assertion about tooling a bit
         | exaggerated, don't you?
        
         | dismalaf wrote:
         | ?? OCaml has had a completion engine for as long as I can
         | remember (definitely over a decade) and it powers their LSP
         | these days. I do know however that the community focuses mostly
         | on Vim and Emacs.
        
         | debugnik wrote:
         | LSP isn't the protocol that interfaces with debuggers, that'd
         | be DAP. You're right that OCaml debugging is kinda clunky at
         | the moment.
         | 
         | OCaml does have an okay LSP implementation though, and it's
         | getting better; certainly more stable than F#'s in my
         | experience, since that comparison is coming up a lot in this
         | comment section.
        
       | ackfoobar wrote:
       | > Sum types: For example, Kotlin and Java (and de facto C#) use a
       | construct associated with inheritance relations called sealing.
       | 
       | This has the benefit of giving you the ability to refer to a case
       | as its own type.
       | 
       | > the expression of sums verbose and, in my view, harder to
       | reason about.
       | 
       | You declare the sum type once, and use it many times. Slightly
       | more verbose sum type declaration is worth it when it makes using
       | the cases cleaner.
        
         | wiseowise wrote:
         | > Slightly more verbose sum type declaration is worth it *when
         | it makes using the cases cleaner.*
         | 
         | Correct. This is not the case when you talk about Java/Kotlin.
         | Just ugliness and typical boilerplate heavy approach of JVM
         | languages.
        
           | gf000 wrote:
           | You mistyped "backwards compatible change" going back to
           | close to 3 decades.
        
           | ackfoobar wrote:
           | > Just ugliness and typical boilerplate heavy approach of JVM
           | languages.
           | 
           | I have provided a case how using inheritance to express sum
           | types can help in the use site. You attacked without
           | substantiating your claim.
        
             | wiseowise wrote:
             | Kotlin's/Java's implementation is just a poor man's
             | implementation of very restricted set of real sum types. I
             | have no idea what
             | 
             | > This has the benefit of giving you the ability to refer
             | to a case as its own type.
             | 
             | means.
        
               | ackfoobar wrote:
               | > I have no idea
               | 
               | I can tell.
               | 
               | Thankfully the OCaml textbook has this explicitly called
               | out.
               | 
               | https://dev.realworldocaml.org/variants.html#combining-
               | recor...
               | 
               | > The main downside is the obvious one, which is that an
               | inline record can't be treated as its own free-standing
               | object. And, as you can see below, OCaml will reject code
               | that tries to do so.
        
               | wiseowise wrote:
               | That's for embedded records. You can have the same thing
               | as Kotlin but with better syntax.
        
               | ackfoobar wrote:
               | If you don't do inline records you either
               | 
               | - create a separate record type, which is no less verbose
               | than Java's approach
               | 
               | - use positional destructuring, which is bug prone for
               | business logic.
               | 
               | Also it's funny that you think OCaml records are "with
               | better syntax". It's a weak part of the language creating
               | ambiguity. People work around this qurik by wrapping
               | every record type in its own module.
               | 
               | https://dev.realworldocaml.org/records.html#reusing-
               | field-na...
        
         | nukifw wrote:
         | In the specific case of OCaml, this is also possible using
         | indexing and GADTs or polymorphic variants. But generally,
         | referencing as its own type serves different purposes. From my
         | point of view, distinguishing between sum branches often tends
         | to result in code that is difficult to reason about and
         | difficult to generalise due to concerns about variance and loss
         | of type equality.
        
           | ackfoobar wrote:
           | Unless you reach an unsound part of the type system I don't
           | see how. Could you provide an example?
        
             | nukifw wrote:
             | - You can use GADTs (https://ocaml.org/manual/5.2/gadts-
             | tutorial.html) and indexes to give a concrete type to every
             | constructors:                 ```ocaml       type _
             | treated_as =         | Int : int -> int treated_as        |
             | Float : float -> float treated_as            let f (Int x)
             | = x + 1 (* val f : int treated_as -> int *)       ```
             | 
             | - You can use the structurale nature of polymorphic
             | variants (https://ocaml.org/manual/5.1/polyvariant.html)
             | ```ocaml       let f = function        | `Foo x ->
             | string_of_int (x + 1)        | `Bar x -> x ^ "Hello"
             | (* val f : [< `Foo of int | `Bar of string] -> string` *)
             | let g = function       | `Foo _ -> ()       | _ -> ()
             | (* val g : [> `Foo of 'a ] -> unit *)       ```
             | 
             | (Notice the difference between `>` and `<` in the
             | signature?)
             | 
             | And since OCaml has also an object model, you can also
             | encoding sum and sealing using modules (and private type
             | abreviation).
        
               | ackfoobar wrote:
               | Oh if you use those features to express what "sum type as
               | subtyping" can, it sure gets confusing. But it's not
               | those things that I want to express that are hard to
               | reason about, the confusing part is the additions to the
               | HM type system.
               | 
               | A meta point: it seems to me that a lot of commenters in
               | my thread don't know that vanilla HM cannot express
               | subtypes. This allows the type system to "run backwards"
               | and you have full type inference without any type
               | annotations. One can call it a good tradeoff but it IS a
               | tradeoff.
        
               | nukifw wrote:
               | Yes and my point was, when you want what you present in
               | the first comment, quoting my post, you have tools for
               | that, available in OCaml. But there is cases, when you do
               | not want to treat each branch of your constructors "as a
               | type", when the encoding of visitors is just rough. This
               | is why I think it is nice to have sum type, to complete
               | product type. So i am not sure why we are arguing :)
        
               | ackfoobar wrote:
               | > So i am not sure why we are arguing :)
               | 
               | I think we agree on a lot of points. The rest is mostly
               | preferences. Some other comments in my thread though...
        
               | nukifw wrote:
               | Ok! (BTW, thanks for the interaction!)
        
         | sunnydiskincali wrote:
         | > This has the benefit of giving you the ability to refer to a
         | case as its own type.
         | 
         | A case of a sum-type is an expression (of the variety so-called
         | a type constructor), of course it has a type.
         | datatype shape =           Circle of real         | Rectangle
         | of real * real         | Point             Circle : real ->
         | shape        Rectangle : real * real -> shape        Point : ()
         | -> shape
         | 
         | A case itself isn't a type, though it has a type. Thanks to
         | pattern matching, you're already unwrapping the parameter to
         | the type-constructor when handling the case of a sum-type. It's
         | all about declaration locality. (real * real) doesn't depend on
         | the existence of shape.
         | 
         | The moment you start ripping cases as distinct types out of the
         | sum-type, you create the ability to side-step exhaustiveness
         | and sum-types become useless in making invalid program states
         | unrepresentable. They're also no longer sum-types. If you have
         | a sum-type of nominally distinct types, the sum-type is
         | contingent on the existence of those types. In a class
         | hierarchy, this relationship is bizarrely reversed and there
         | are knock-on effects to that.
         | 
         | > You declare the sum type once, and use it many times.
         | 
         | And you typically write many sum-types. They're disposable. And
         | more to the point, you also have to read the code you write.
         | The cost of verbosity here is underestimated.
         | 
         | > Slightly more verbose sum type declaration is worth it when
         | it makes using the cases cleaner.
         | 
         | C#/Java don't actually have sum-types. It's an incompatible
         | formalism with their type systems.
         | 
         | Anyways, let's look at these examples:
         | 
         | C#:                 public abstract record Shape;       public
         | sealed record Circle(double Radius) : Shape;       public
         | sealed record Rectangle(double Width, double Height) : Shape;
         | public sealed record Point() : Shape;              double
         | Area(Shape shape) => shape switch       {           Circle c =>
         | Math.PI * c.Radius * c.Radius,           Rectangle r => r.Width
         | * r.Height,           Point => 0.0,           _ => throw new
         | ArgumentException("Unknown shape", nameof(shape))       };
         | 
         | ML:                 datatype shape =           Circle of real
         | | Rectangle of real * real         | Point              val
         | result =         case shape of             Circle r => Math.pi
         | * r * r           | Rectangle (w, h) => w * h           | Point
         | => 0.0
         | 
         | They're pretty much the same outside of C#'s OOP quirkiness
         | getting in it's own way.
        
           | ackfoobar wrote:
           | > The moment you start ripping cases as distinct types out of
           | the sum-type, you create the ability to side-step
           | exhaustiveness and sum-types become useless in making invalid
           | program states unrepresentable.
           | 
           | Quite the opposite, that gives me the ability to explicitly
           | express what kinds of values I might return. With your shape
           | example, you cannot express in the type system "this function
           | won't return a point". But with sum type as sealed
           | inheritance hierarchy I can.
           | 
           | > C#/Java don't actually have sum-types.
           | 
           | > They're pretty much the same
           | 
           | Not sure about C#, but in Java if you write `sealed`
           | correctly you won't need the catch-all throw.
           | 
           | If they're not actual sum types but are pretty much the same,
           | what good does the "actually" do?
        
             | tomsmeding wrote:
             | > Not sure about C#, but in Java if you write `sealed`
             | correctly you won't need the catch-all throw.
             | 
             | Will the compiler check that you have handled all the cases
             | still? (Genuinely unsure -- not a Java programmer)
        
               | ackfoobar wrote:
               | https://openjdk.org/jeps/409#Sealed-classes-and-pattern-
               | matc...
        
             | sunnydiskincali wrote:
             | > With your shape example, you cannot express in the type
             | system "this function won't return a point".
             | 
             | Sure you can, that's just subtyping. If it returns a value
             | that's not a point, the domain has changed from the shape
             | type and you should probably indicate that.
             | structure Shape = struct         datatype shape =
             | Circle of real           | Rectangle of real * real
             | | Point       end            structure Bound = struct
             | datatype shape =             Circle of real           |
             | Rectangle of real * real       end
             | 
             | This is doing things quick and dirty. For this trivial
             | example it's fine, and I think a good example of why making
             | sum-types low friction is a good idea. It completely
             | changes how you solve problems when they're fire and forget
             | like this.
             | 
             | That's not to say it's the only way to solve this problem,
             | though. And for heavy-duty problems, you typically write
             | something like this using higher-kinded polymorphism:
             | signature SHAPE_TYPE = sig         datatype shape =
             | Circle of real           | Rectangle of real * real
             | | Point              val Circle : real -> shape         val
             | Rectangle : real * real -> shape         val Point : shape
             | end            functor FullShape () : SHAPE_TYPE = struct
             | datatype shape =             Circle of real           |
             | Rectangle of real * real           | Point              val
             | Circle = Circle         val Rectangle = Rectangle
             | val Point = Point       end            functor RemovePoint
             | (S : SHAPE_TYPE) :> sig         type shape         val
             | Circle : real -> shape         val Rectangle : real * real
             | -> shape       end = struct         type shape = S.shape
             | val Circle = S.Circle         val Rectangle = S.Rectangle
             | end                 structure Shape = FullShape()
             | structure Bound = RemovePoint(Shape)
             | 
             | This is extremely overkill for the example, but it also
             | demonstrates a power you're not getting out of C# or Java
             | without usage of reflection. This is closer to the system
             | of inheritance, but it's a bit better designed. The added
             | benefit here over reflection is that the same principle of
             | "invalid program states are unrepresentable" applies here
             | as well, because it's the exact same system being used.
             | You'll also note that even though it's a fair bit closer
             | conceptually to classes, the sum-type is still distinct.
             | 
             | Anyways, in both cases, this is now just:
             | DoesNotReturnPoint : Shape.shape -> Bound.shape
             | 
             | Haskell has actual GADTs and proper higher kinded
             | polymorphism, and a few other features where this all looks
             | very different and much terser. Newer languages bake
             | subtyping into the grammar.
             | 
             | > If they're not actual sum types but are pretty much the
             | same, what good does the "actually" do?
             | 
             | Conflation of two different things here. The examples given
             | are syntactically similar, and they're both treating the
             | constituent part of the grammar as a tagged union. The case
             | isn't any cleaner was the point.
             | 
             | However in the broader comparison between class hierarchies
             | and sum-types? They're not similar at all. Classes can do
             | some of the things that sum-types can do, but they're
             | fundamentally different and encourage a completely
             | different approach to problem-solving, conceptualization
             | and project structure... in all but the most rudimentary
             | examples. As I said, my 2nd example here is far closer to a
             | class-hierarchy system than sum-types, though it's still
             | very different. And again, underlining that because of the
             | properties of sum-types, thanks to their specific
             | formalization, they're capable of things class hierarchies
             | aren't. Namely, enforcing valid program-states at a type-
             | level. Somebody more familiar with object-oriented
             | formalizations may be a better person to ask than me on why
             | that is the case.
             | 
             | It's a pretty complicated space to talk about, because
             | these type systems deviate on a very basic and fundamental
             | level. Shit just doesn't translate well, and it's easy to
             | find false friends. Like how the Japanese word for "name"
             | sounds like the English word, despite not being a loan
             | word.
        
               | ackfoobar wrote:
               | You wrote a lot of words to say very little.
               | 
               | Anyway, to translate your example:
               | public abstract sealed class Shape permits Point, Bound
               | {}         public final class Point extends Shape {}
               | public abstract sealed class Bound permits Circle,
               | Rectangle {}         public final class Circle extends
               | Bound {}         public final class Rectangle extends
               | Bound {}
               | 
               | A `Rectangle` is both a `Bound` (weird name choice but
               | whatever), and a `Shape`. Thanks to subtyping, no
               | contortion needed.
               | 
               | > the Japanese word for "name" sounds like the English
               | word, despite not being a loan word.
               | 
               | Someone from the Java team explicitly said they're
               | drawing inspirations from ML.
               | 
               | https://news.ycombinator.com/item?id=24203363
        
       | _mu wrote:
       | I haven't worked in OCaml but I have worked a bit in F# and found
       | it to be a pleasant experience.
       | 
       | One thing I am wondering about in the age of LLMs is if we should
       | all take a harder look at functional languages again. My thought
       | is that if FP languages like OCaml / Haskell / etc. let us
       | compress a lot of information into a small amount of text, then
       | that's better for the context window.
       | 
       | Possibly we might be able to put much denser programs into the
       | model and one-shot larger changes than is achievable in languages
       | like Java / C# / Ruby / etc?
        
         | nukifw wrote:
         | To be completely honest, I currently only use LLMs to assist me
         | in writing documentation (and translating articles), but I know
         | that other people are looking into it:
         | https://anil.recoil.org/wiki?t=%23projects
        
         | d4mi3n wrote:
         | I think this is putting the cart before the horse. Programs are
         | generally harder to read than they are to write, so optimizing
         | for concise output to benefit the tool at the potential expense
         | of the human isn't a trade I'd personally make.
         | 
         | Granted, this may just be an argument for being more
         | comfortable reading/writing code in a particular style, but
         | even without the advantages of LLMs adoption of functional
         | paradigms and tools has been a struggle.
        
         | gf000 wrote:
         | My completely non-objective experiment of writing a simple CLI
         | game in C++ and Haskell shows that the lines of code were
         | indeed less in case of Haskell.. but the number of words were
         | roughly the same, meaning the Haskell code just "wider" instead
         | of "higher".
         | 
         | And then I didn't even make this "experiment" with Java or
         | another managed, more imperative language which could have shed
         | some weight due to not caring about manual memory management.
         | 
         | So not sure how much truth is in there - I think it differs
         | based on the given program: some lend itself better for an
         | imperative style, others prefer a more functional one.
        
         | esafak wrote:
         | I think LLMs benefit from training examples, static typing, and
         | an LSP implementation more than terseness.
        
           | nextos wrote:
           | Exactly. My experience building a system that generates Dafny
           | and Liquid Haskell is that you can get much further than with
           | a language that is limited to dynamic or simple static types.
        
         | jappgar wrote:
         | That was my optimistic take before I started working on a large
         | Haskell code base.
         | 
         | Aside from the obvious problem that there's not enough FP in
         | the training corpus, it seems like terser languages don't work
         | all that well with LLMs.
         | 
         | My guess is that verbosity actually helps the generation self-
         | correct... if it predicts some "bad" tokens it can pivot more
         | easily and still produce working code.
        
           | sshine wrote:
           | > _terser languages don 't work all that well with LLMs_
           | 
           | I'd believe that, but I haven't tried enough yet. It seems to
           | be doing quite well with jq. I wonder how its APL fares.
           | 
           | When Claude generates Haskell code, I constantly want to
           | reduce it. Doing that is a very mechanical process; I wonder
           | if giving an agent a linter would give better results than
           | overloading it all to the LLM.
        
             | willhslade wrote:
             | Apl is executed right to left and LLMS.... Aren't.
        
           | yawaramin wrote:
           | There's actually a significant difference between Haskell and
           | OCaml here so we can't lump them together. OCaml is a
           | significantly simpler, and moderately more verbose, language
           | than Haskell. That helps LLMs when they do codegen.
        
           | b_e_n_t_o_n wrote:
           | This has been my experience as well. Ai writes Go better than
           | any language besides maybe html and JavaScript/python.
        
         | sshine wrote:
         | > _My thought is that if FP languages like OCaml / Haskell /
         | etc. let us compress a lot of information into a small amount
         | of text, then that's better for the context window._
         | 
         | Claude Code's Haskell style is very verbose; if-then-elsey,
         | lots of nested case-ofs, do-blocks at multiple levels of
         | intension, very little naming things at top-level.
         | 
         | Given a sample of a simple API client, and a request to do the
         | same but for another API, it did very well.
         | 
         | I concluded that I just have more opinions about Haskell than
         | Java or Rust. If it doesn't look nice, why even bother with
         | Haskell.
         | 
         | I reckon that you could seed it with style examples that take
         | up very little context space. Also, remind it to not enable
         | language pragmas per file when they're already in .cabal, and
         | similar.
        
         | seprov wrote:
         | Procedures can be much more concise in functional/ML syntax,
         | but many things are not -- dependency injection in languages
         | like C# for example are able to be much less verbose because of
         | really excellent DI libraries and (arguably more sane) instance
         | constructor syntax.
        
         | dkarl wrote:
         | In Scala, I've had excellent luck using LLMs to speed up
         | development when I'm using cats-effect, an effects library.
         | 
         | My experience in the past with something like cats-effect has
         | been that there are straightforward things that aren't obvious,
         | and if you haven't been using it recently, and maybe even if
         | you've been using it but haven't solved a similar problem
         | recently, you can get stuck trawling through the docs squinting
         | at type signatures looking for what turns out to be, in
         | hindsight, an elegant and simple solution. LLMs have vastly
         | reduced this kind of friction. I just ask, "In cats-effect, how
         | do I...?" and 80% of the time the answer gets me immediately
         | unstuck. The other 20% of the time I provide clarifying context
         | or ask a different LLM.
         | 
         | I haven't done enough maintenance coding yet to know if this
         | will radically shift my view of the cost/benefit of functional
         | programming with effects, but I'm very excited. Writing cats-
         | effect code has always been satisfying and frustrating in equal
         | measure, and so far, I'm getting the confidence and correctness
         | with a fraction of the frustration.
         | 
         | I haven't unleashed Claude Code on any cats-effect code yet.
         | I'm curious to see how well it will do.
        
       | raphinou wrote:
       | Some years ago I also wanted to make ocaml my primary language,
       | but rapidly encountered problems: difficulty to install (on Linux
       | due to the requirement of a very unusual tool which name and
       | function I forgot), no response from community regarding how to
       | solve that problem, no solid postgresql driver, ....
       | 
       | Wanting to use a functional language I pivoted to fsharp, which
       | was not the expected choice for me as I use Linux exclusively. I
       | have been happy with this choice, it has even become my preferred
       | language. The biggest problem for me was the management of the
       | fsharp community, the second class citizen position of fsharp in
       | the DotNet ecosystem, and Microsoft's action screwing the
       | goodwill of the dev community (eg hot reload episode). I feel
       | this hampered the growth of the fsharp community.
       | 
       | I'm now starting to use rust, and the contrast on these points
       | couldn't be bigger.
       | 
       | Edit: downvoters, caring to share why? I thought sharing my
       | experience would have been appreciated. Would like to know why I
       | was wrong.
        
         | johnisgood wrote:
         | > difficulty to install
         | 
         | Use opam: https://opam.ocaml.org or
         | https://opam.ocaml.org/doc/Install.html.
         | 
         | Additionally, see: https://ocaml.org/install#linux_mac_bsd and
         | https://ocaml.org/docs/set-up-editor.
         | 
         | It is easy to set up with Emacs, for example. VSCodium has
         | OCaml extension as well.
         | 
         | All you need for the OCaml compiler is opam, it handles all the
         | packages and the compiler.
         | 
         | For your project, use dune:
         | https://dune.readthedocs.io/en/stable/quick-start.html.
        
           | Milpotel wrote:
           | "use opam" is always the answer but in reality its the worst
           | package manager ever. I've never seen so many packages fail
           | to install, so many broken dependencies and miscompilations
           | that resulted in segfaults due to wrong dependencies. I just
           | gave up with Ocaml due to the crappy ecosystem, although I
           | could have lived with the other idiosyncrasies.
        
             | nukifw wrote:
             | There is a lot of work on Dune Package Management that will
             | fix some legacy issues related to OPAM,
             | https://dune.readthedocs.io/en/stable/tutorials/dune-
             | package... !! Stay tuned!
        
               | johnisgood wrote:
               | Dune is not a dependency manager, it is a build tool.
               | Opam is the dependency manager. By default, Dune doesn't
               | fetch dependencies, opam does that. That said, Dune does
               | use opam, yeah.
        
               | nukifw wrote:
               | And the next milestone of Dune is to become an
               | alternative package manager via Dune package Management,
               | using a store in a "nixish" way.
        
               | johnisgood wrote:
               | I suppose it is still going to use opam internally,
               | right?
        
               | yawaramin wrote:
               | No, it is doing its own package management
               | implementation.
        
               | johnisgood wrote:
               | So dune is going to replace opam? Where can I read more
               | about this if so?
        
               | nukifw wrote:
               | Package description, but it use its own engine.
        
             | johnisgood wrote:
             | I have never had issues and been writing OCaml and using
             | opam for years.
             | 
             | Can you be more specific?
             | 
             | (Why the down-vote? He does need to be specific, right?
             | Additionally, my experiences are somehow invalid?)
        
             | sestep wrote:
             | And even if you do get opam working for a project, it's not
             | at all reproducible and will just randomly break at some
             | point in the future. For instance, I had this in a
             | Dockerfile for one project:                 RUN eval $(opam
             | env) && opam install --yes dune=3.7.0
             | 
             | One day the build just randomly broke. Had to replace it
             | with this:                 RUN eval $(opam env) && opam
             | install --yes dune=3.19.1
             | 
             | Not a big change, but the fact that this happens at all is
             | just another part of building with OCaml feeling like
             | building on a foundation of sand. Modern languages have at
             | least learned how to make things reproducible with e.g.
             | lockfiles, but OCaml has not.
        
               | johnisgood wrote:
               | Use "opam lock" and "opam pin". Additionally, Dune's
               | lockdir feature uses opam's solver internally to generate
               | a lock directory containing ".opam.locked" files for
               | every dependency. This is Dune's way of having fully
               | reproducible builds without relying on opam's switch
               | state alone.
               | 
               | Additionally, see:
               | https://dune.readthedocs.io/en/stable/tutorials/dune-
               | package....
        
               | sestep wrote:
               | No, the problem is that dune=3.7.0 got removed from the
               | registry entirely.
        
               | johnisgood wrote:
               | It is still there according to "opam show dune --all-
               | versions", so no, it has not been removed.
               | 
               | Anyways, what you could do is:                 opam pin
               | add dune 3.7.0       opam install dune
               | 
               | Alternatively, you can use the tarball directly:
               | opam pin add dune https://github.com/ocaml/dune/releases/
               | download/3.7.0/dune-3.7.0.tbz
        
             | debugnik wrote:
             | I mean, it's quite clunky, but on Linux or WSL I've never
             | had the broken experience you talk about. Could you share
             | your setup? Was this maybe on bare macOS or Windows, in
             | which case I totally believe it because they've been
             | neglected?
        
         | davidwritesbugs wrote:
         | I abandoned ocaml just because I couldn't get a stepping
         | debugger to work. Can't remember the exact issues but I tried
         | to install in vscode to no avail & I've no interest in emacs
        
       | loxs wrote:
       | I migrated from OCaml to Rust around 2020, haven't looked back.
       | Although Rust is quite a lot less elegant and has some unpleasant
       | deficiencies (lambdas, closures, currying)... and I end up having
       | to close one one eye sometimes and clone some large data-
       | structure to make my life easier... But regardless, its huge
       | ecosystem and great tooling allows me to build things
       | comparatively so easily, that OCaml has no chance. As a bonus,
       | the end result is seriously faster - I know because I rewrote one
       | of my projects and for some time I had feature parity between the
       | OCaml and Rust versions.
       | 
       | Nevertheless, I have fond memories of OCaml and a great amount of
       | respect for the language design. Haven't checked on it since,
       | probably should. I hope part of the problems have been solved.
        
         | ackfoobar wrote:
         | > the end result is seriously faster
         | 
         | Do you have a ballpark value of how much faster Rust is? Also I
         | wonder if OxCaml will be roughly as fast with less effort.
        
         | jasperry wrote:
         | Your comment makes me think the kind of people who favor OCaml
         | over Rust wouldn't necessarily value a huge ecosystem or the
         | most advanced tooling. They're the kind who value the elegance
         | aspect above almost all else, and prefer to build things from
         | the ground up, using no more than a handful of libraries and a
         | very basic build procedure.
        
         | javcasas wrote:
         | Were you using the ocamlopt compiler? By default, ocaml runs in
         | a VM, but few people figure that out because it is not
         | screaming its name all the time like a pokemon (looking at you
         | JVM/CLR). But ocaml can be compiled to machine code with
         | significant performance improvements.
        
           | debugnik wrote:
           | [delayed]
        
       | FrustratedMonky wrote:
       | In F# comparison. Modules "my opinion, strongly justify
       | preferring one over the other".
       | 
       | Strong stance on Modules. My ignorance, what do they do that
       | provides that much benefit. ??
        
         | debugnik wrote:
         | In short, OCaml modules are used for coarse-grained generics.
         | 
         | Modules are like structurally-typed records that can contain
         | both abstract types and values/functions dependent on those
         | types; every implementation file is itself a module. When
         | passed to functors (module-level functions), they allow you to
         | parameterize large pieces of code, depending on multiple types
         | and functions, all at once quite cleanly. And simply including
         | them or narrowing their signatures is how one exports library
         | APIs.
         | 
         | (The closest equivalent I can imagine to module signatures is
         | Scala traits with abstract type members, but structurally-typed
         | and every package is an instance.)
         | 
         | However, they are a bit too verbose for finer-grained generics.
         | For example, a map with string keys needs `module String_map =
         | Map.Make(String)`. There is limited support for passing modules
         | as first-class values with less ceremony, hopefully with more
         | on the way.
        
         | akkad33 wrote:
         | I don't know OCAML well but I think this is referring to the
         | fact that modules on OCAML can be generic. In f# there is no
         | HKTs that is types that can be parameterised with type classes.
         | So in F# you have to have List.map, option.map etc, whereas in
         | a language like OCAML or Haskell they would have one
         | parametrised module
        
       | noelwelsh wrote:
       | I saw a talk by someone from Google about their experiences using
       | Rust in the Android team. Two points stuck out: they migrated
       | many projects from Python, so performance can't have been that
       | much of a concern, and in their surveys the features people liked
       | most were basics like pattern matching and ADTs. My conclusion is
       | that for a lot of tasks the benefit from Rust came from ML cicra
       | 1990, not lifetimes etc. I feel if OCaml had got its act together
       | around about 2010 with multicore and a few other annoyances[1] it
       | could have been Rust. Unfortunately it fell into the gap between
       | what academia could justify working on and what industry was
       | willing to do.
       | 
       | [1]: Practically speaking, the 31-bit Ints are annoying if you're
       | trying to do any bit bashing, but aesthetically the double
       | semicolons are an abomination and irk me far more.
        
         | unstruktured wrote:
         | There is absolutely no reason to use double semicolons in
         | practice. The only place you really should see it is when using
         | the repl.
        
           | sigzero wrote:
           | Yeah, it makes me think he doesn't understand them in OCaml.
        
         | garbthetill wrote:
         | doesnt rust still have the advantage of having no gc? I dont
         | like writing rust, but the selling point of being able to write
         | performative code with memory safety guarantees has always
         | stuck with me
        
           | noelwelsh wrote:
           | I think "no gc but memory safe" is what originally got people
           | excited about Rust. It's a genuinely new capability in
           | production ready languages. However, I think Rust is used in
           | many contexts where a GC is just fine _and_ working with
           | lifetimes makes many programs more painful to write. I think
           | for many programs the approach taken by Oxidized OCaml[1] or
           | Scala[2] gives 80% of the benefit while being a lot more
           | ergonomic to work with.
           | 
           | When I see Rust topping the "most loved language" on Stack
           | Overflow etc. what I think is really happening is that people
           | are using a "modern" language for the first time. I
           | consistently see people gushing about, e.g., pattern matching
           | in Rust. I agree pattern matching is awesome, but it is also
           | not at all novel if you are a PL nerd. It's just that most
           | commercial languages are crap from a PL nerd point of view.
           | 
           | So I think "no gc but memory safe" is what got people to look
           | at Rust, but it's 1990s ML (ADTs, pattern matching, etc.)
           | that keeps them there.
           | 
           | [1]: https://github.com/oxcaml/oxcaml
           | 
           | [2]: https://docs.scala-
           | lang.org/scala3/reference/experimental/cc...
        
             | GeekyBear wrote:
             | > I think "no gc but memory safe" is what originally got
             | people excited about Rust.
             | 
             | I think it was more about "the performance of C, but with
             | memory safety and data race safety".
        
           | vips7L wrote:
           | You can write safe and performative code with a garbage
           | collector. They're not mutually exclusive. It just depends on
           | your latency and throughput requirements.
        
           | 0cf8612b2e1e wrote:
           | Considering how many applications are running in JS/Python
           | execution speed or GC is a low concern for many programs.
           | Ergonomics (community, compiler guarantees, distribution,
           | memory pressure, talent availability, whatever) seem more
           | meaningful.
        
         | nine_k wrote:
         | I'd say that Google strives to have a reasonably short list of
         | languages approved for production-touching code. Rust can
         | replace / complement C++, while OCaml cannot (it could replace
         | Go instead... fat chance!). So I suspect that the team picked
         | Rust because it was the only blessed language with ADTs, not
         | because they won't like something with faster compile times.
         | 
         | No way OCaml could have stolen the Rust's thunder: we have a
         | number of very decent and performant GC-based languages, from
         | Go to Haskell; we only had one bare-metal-worthy expressive
         | language in 2010, C++, and it was pretty terrible (still is,
         | but before C++11 and C++17 it was even more terrible).
        
           | hardwaregeek wrote:
           | Wouldn't Kotlin be a more reasonable choice in that case? It
           | has ADTs and a lot of the same niceties of Rust.
        
             | actionfromafar wrote:
             | Garbage collector in Kotlin makes it a no go for C or C++
             | displacement.
        
             | Artamus wrote:
             | I'm inclined to think that the Python -> Rust was only for
             | some odds and ends. I know the biggest recipient of Rust
             | trainings was the Android platform team at first, which I
             | think also used a lot of C++.
             | 
             | Kotlin is definitely available at Google, but when talking
             | about sym types et al it's not nearly as nice to use as
             | Rust / OCaml.
        
             | sureglymop wrote:
             | Yes. It can also be compiled to native. I just think it was
             | held back too much by the java/jvm backwards compatibility
             | but then again that's probably also the justification for
             | its existence.
             | 
             | I definitely find it (and jetpack compose) make developing
             | android apps a much better experience than it used to be.
             | 
             | What I like a lot about Kotlin are its well written
             | documentation and the trailing lambdas feature. That is
             | definitely directly OCaml inspired (though I also recently
             | saw it in a newer language, the "use" feature in Gleam).
             | But in Kotlin it looks nicer imo. Allows declarative code
             | to look pretty much like json which makes it more beginner
             | friendly than the use syntax.
             | 
             | But Kotlin doesn't really significantly stand out among
             | Java, C#, Swift, Go, etc. And so it is kind of doomed to be
             | a somewhat domain specific language imo.
        
         | troupo wrote:
         | OCaml also needed the brief but bright ReasonML moment to
         | add/fix/improve some of the syntax IIRC and work on user-
         | friendly error messages. But this should've definitely happened
         | much much earlier than it did.
        
           | Taikonerd wrote:
           | I would say ReasonML also needed more follow-through. It
           | seems like the OCaml community hasn't really rallied behind
           | it.
        
             | mirekrusin wrote:
             | Maybe it's good it died? Now we have moonbit lang.
        
             | bsder wrote:
             | It doesn't help that the OCaml community also has the
             | problem that a significant minority seem to resent the fact
             | that one company (Jane Street) has written more OCaml than
             | the the rest of the world combined and then some and so de
             | facto controls the ecosystem.
             | 
             | Whereas the Go and Rust communities, for example, were just
             | fine with having corporate sponsorship driving things.
        
               | debugnik wrote:
               | > and so de facto controls the ecosystem
               | 
               | They really don't, less than 5% of opam packages depend
               | on Base and that's their bottom controversial dependency
               | I'd say. Barely anyone's complaining about their work on
               | the OCaml platform or less opinionated libraries. I admit
               | the feeling that they do lingers, but having used OCaml
               | in anger for a few years I think it's a non-issue.
               | 
               | What they do seem to control is the learning pipeline, as
               | a newcomer you find yourself somewhat funneled to Base,
               | Core, etc. I tried them for a while, but eventually
               | understood I don't really need them.
        
         | the__alchemist wrote:
         | I'm with you. I think some of the nicest parts of rust have
         | nothing to do with memory safety; they're ways to structure
         | your program as you mention.
        
         | munificent wrote:
         | _> I feel if OCaml had got its act together around about 2010
         | with multicore and a few other annoyances[1] it could have been
         | Rust. _
         | 
         | Arguably, that could have been Scala and for a while it seemed
         | like it would be Scala but then it kind of just... didn't.
         | 
         | I suspect some of that was that the programming style of some
         | high profile Scala packages really alienated people by pushing
         | the type system and operator overloading much farther than
         | necessary.
        
         | yodsanklai wrote:
         | > aesthetically the double semicolons are an abomination and
         | irk me far more.
         | 
         | I think they have been optional for like 20 years, except in
         | the top-level interactive environment to force execution.
         | 
         | That being said, I still don't get why people are so much upset
         | with the syntax. You'll integrate it after a week writing OCaml
         | code.
        
       | nine_k wrote:
       | I wish somebody with this amount of experience would compare the
       | benefits / shortcomings of using the ReasonML syntax. (The
       | article mentions it once, in passing.)
        
         | hardwaregeek wrote:
         | I like Reason syntax and I wish it was more common, but I think
         | if you want to engage in the OCaml community it's probably
         | better to just bite the bullet and use the standard syntax.
         | It's what almost everybody uses so you'll need to understand it
         | to read any code or documentation in the ecosystem
        
         | myaccountonhn wrote:
         | I don't have extensive experience but the little I did was
         | issues with LSP not working as well.
        
         | globuous wrote:
         | What I missed most were let bindings
         | (https://ocaml.org/manual/5.3/bindingops.html)
         | 
         | ReasonML has custom operators that allows for manipulating
         | monads somewhat sanely (>>= operators and whatnot). rescript
         | (reasonml's "fork") did not last time I checked. But it does
         | have an async/await syntax which helps a lot with async code.
         | reasonml did not last time I checked, so you had to use raw
         | promises.
         | 
         | I believe Melange (which the article briefly talks about)
         | supports let bindings with the reason syntax.
         | 
         | And this kinda changes everything if you React. Because you can
         | now have sane JSX with let bindings. Which you could not until
         | melange. Indeed, you can PPX your way out of it in ocaml
         | syntax, but I'm not sure the syntax highlight works well in
         | code editors. It did not on mine anyway last time I checked.
         | 
         | So for frontend coding, Melange's reason ml is great as you
         | have both, and let bindings can approximate quite well async
         | syntax on top of writing readable monadic code.
         | 
         | For backend code, as a pythonista, I hate curlies. and I do
         | like parenthesis-less function calls and definitions a lot. But
         | I still have a lot of trouble, as a beginner ocamler, with non-
         | variable function argument as I need to do "weird" parenthesis
         | stuff.
         | 
         | Hope this "helps"!
        
         | nukifw wrote:
         | Sorry, I never used ReasonML so I don't see any advantage of
         | using ReasonML except it had the time to die twice in 4 years
         | :)
        
       | garbthetill wrote:
       | What a brilliant article, it really puts to rest for me, the
       | whole "why not use F#?" argument. In almost every OCaml thread,
       | someone suggests F# as a way to sidestep OCaml's tooling.
       | 
       | I've always been curious about OCaml, especially since some
       | people call it "Go with types" and I'm not a fan of writing Rust.
       | But I'm still not sold on OCaml as a whole, its evangelists just
       | don't win me over the way the Erlang, Ruby, Rust, or Zig folks
       | do. I just cant see the vision
        
         | debugnik wrote:
         | Funny, I moved to OCaml to sidestep F# tooling. At least last
         | time I used F#: Slow compiler, increasingly C#-only ecosystem,
         | weak and undocumented MSBuild (writing custom tasks would
         | otherwise be nice!), Ionide crashes, Fantomas is unsound...
         | 
         | But OCaml sadly can't replace F# for all my use cases. F# does
         | get access to many performance-oriented features that the CLR
         | supports and OCaml simply can't, such as value-types. Maybe
         | OxCaml can fix that long term, but I'm currently missing a
         | performant ML-like with a simple toolchain.
        
         | joshmarlow wrote:
         | It's been a few years since I've touched OCaml - the ecosystem
         | just wasn't what I wanted - but the core language is still my
         | favorite.
         | 
         | And the best way I can describe why is that my code generally
         | ends up with a few heavy functions that do too much; I can fix
         | it once I notice it, but that's the direction my code tends to
         | go in.
         | 
         | In my OCaml code, I would look for the big function and... just
         | not find it. No single workhorse that does a lot - for some
         | reason it was just easier for me to write good code.
         | 
         | Now I do Rust for side projects because I like the type system
         | - but I would prefer OCaml.
         | 
         | I keep meaning to checkout F# though for all of these reasons.
        
       | manoDev wrote:
       | I'm sure there's merit to the language, but the syntax seems
       | absolutely alien to me. Some attempt to look like verbose
       | imperative code, a bunch of semicolons, and for some strange
       | reason, hate of parenthesis.
       | 
       | Real life sample:                   let print_expr exp =
       | (* Local function definitions *)             let open_paren prec
       | op_prec =               if prec > op_prec then print_string "("
       | in             let close_paren prec op_prec =               if
       | prec > op_prec then print_string ")" in             let rec print
       | prec exp =     (* prec is the current precedence *)
       | match exp with                 Const c -> print_float c
       | | Var v -> print_string v               | Sum(f, g) ->
       | open_paren prec 0;                   print 0 f; print_string " +
       | "; print 0 g;                   close_paren prec 0
       | | Diff(f, g) ->                   open_paren prec 0;
       | print 0 f; print_string " - "; print 1 g;
       | close_paren prec 0               | Prod(f, g) ->
       | open_paren prec 2;                   print 2 f; print_string " *
       | "; print 2 g;                   close_paren prec 2
       | | Quot(f, g) ->                   open_paren prec 2;
       | print 2 f; print_string " / "; print 3 g;
       | close_paren prec 2             in print 0 exp;;
       | 
       | A function is defined as:                   let print_expr exp =
       | 
       | That seems pretty hard to read at a glance, and easy to mistype
       | as a definition.
       | 
       | Also, you need to end the declaration with `in`?
       | 
       | Then, semicolons...                   open_paren prec 0;
       | print 0 f; print_string " + "; print 0 g;
       | 
       | ... and even double semicolons ...                   print 0
       | exp;;
       | 
       | That looks like a language you really want an IDE helping you
       | with.
        
         | myaccountonhn wrote:
         | That code is probably some of the hardest you'll encounter in
         | Ocaml, but for me its quite obvious what it does and easy to
         | read because I've worked with GADTs before. If you haven't then
         | its you'll need to study and understand them to understand the
         | code.
         | 
         | I actually really like the syntax of OCaml, its very easy to
         | write and when you're used to it, easy to read (easier than
         | reasonml IMO).
         | 
         | Double semicolons are afaik only used in the repl.
        
         | UncleOxidant wrote:
         | I mean, I guess it depends on your background, but that code
         | looks pretty nice compared to how it would look in a language
         | without pattern matching and ADTs. This is why the MLs excel
         | for things like parsers, interpreters, and compilers. Beauty is
         | in the eye of the beholder, I guess. I suspect that if you gave
         | it a bit of time it would start to really grow on you - that's
         | how it was in my case. At first: "WTF is this weird syntax?!",
         | a few weeks in "oh, this makes a lot of sense, actually", a few
         | years "Yeah, I'd much rather write this sort of thing in OCaml
         | (or an ML in general)"
        
           | manoDev wrote:
           | I don't find the ADT and matching part weird, but rather
           | everything else (as mentioned).
        
         | javcasas wrote:
         | `let` defines a value. Some values are "plain old values", some
         | are functions.
         | 
         | The syntax is `let <constantname> <parameters> = <value> in
         | <expression>;;` where `expression` can also have `let`
         | bindings.
         | 
         | So you can have                 let one = 1 in       let two =
         | 2 in       let three = one + two in       print_endline
         | (string_of_int three);;
        
           | jallmann wrote:
           | The way I always think about it is in terms of scope. With:
           | let x = v in expr
           | 
           | `x` is now available for use in `expr`
           | 
           | In essence, an OCaml program is a giant recursive expression,
           | because `expr` can have its own set of let definitions.
           | 
           | In the REPL, this is where the double semicolons come in, as
           | a sort of hint to continue processing after the expression.
        
         | javcasas wrote:
         | It also uses double semicolon because single semicolon is
         | already used as statement separator kinda like C. So double
         | semicolon is top-level statement terminator.
        
         | jallmann wrote:
         | > That seems pretty hard to read at a glance, and easy to
         | mistype as a definition.
         | 
         | YMMV but let expressions are one of the nice things about OCaml
         | - the syntax is very clean in a way other languages aren't.
         | Yes, the OCaml syntax has some warts, but let bindings aren't
         | one of them.
         | 
         | It's also quite elegant if you consider how multi-argument let
         | can be decomposed into repeated function application, and how
         | that naturally leads to features like currying.
         | 
         | > Also, you need to end the declaration with `in`?
         | 
         | Not if it's a top level declaration.
         | 
         | It might make more sense if you think of the `in` as a scope
         | operator, eg `let x = v in expr` makes `x` available in `expr`.
         | 
         | > Then, semicolons...
         | 
         | Single semicolons are syntactic sugar for unit return values.
         | Eg,                 print_string "abc";
         | 
         | is the same as                 let () = print_string "abc" in
        
       | jasperry wrote:
       | Question about terminology: Is it common to call higher-order
       | function types "exponential types" as the article does? I know
       | what higher-order functions are, but am having trouble grasping
       | why the types would be called "exponential".
        
         | nukifw wrote:
         | Usually we speaking only about sum and product (because article
         | usually refers to ADT, so Algebraic Data type). A function is
         | not really Data, so it is not included. But you can use the
         | same tricks (ie: a -> b has arity b^a) to compute the number of
         | potential inhabitant
        
         | ackfoobar wrote:
         | A first-order function type is already exponential.
         | 
         | A sum type has as many possible values as the sum of its cases.
         | E.g. `A of bool | B of bool` has 2+2=4 values. Similarly for
         | product types and exponential types. E.g. the type bool -> bool
         | has 2^2=4 values (id, not, const true, const false) if you
         | don't think about side effects.
        
           | jolmg wrote:
           | > bool -> bool has 2^2=4 values
           | 
           | Not the best example since 2*2=4 also.
           | 
           | How about this bit of Haskell:                 f :: Bool ->
           | Maybe Bool
           | 
           | That's 3 ^ 2 = 9, right?                 f False = Nothing
           | f False = Just True       f False = Just False       f True =
           | Nothing       f True = Just True       f True = Just False
           | 
           | Those are 6. What would be the other 3? or should it actually
           | be a*b=6?
           | 
           | EDIT: Nevermind, I counted wrong. Here are the 9:
           | f x = case x of         True -> Nothing         False ->
           | Nothing            f x = case x of         True -> Nothing
           | False -> Just False            f x = case x of         True
           | -> Nothing         False -> Just True            f x = case x
           | of         True -> Just False         False -> Nothing
           | f x = case x of         True -> Just False         False ->
           | Just False            f x = case x of         True -> Just
           | False         False -> Just True            f x = case x of
           | True -> Just True         False -> Nothing            f x =
           | case x of         True -> Just True         False -> Just
           | False            f x = case x of         True -> Just True
           | False -> Just True
        
             | ackfoobar wrote:
             | Good point, well there's Ordering type built-in in Haskell
             | (LT | EQ | GT). Ordering -> bool has 2^3=8 values (const
             | true, const false, == LT, == EQ, == GT, is_lte, is_gte, ne)
             | 
             | EDIT: now you see why I used the smallest type possible to
             | make my point. Exponentials get big FAST (duh).
        
             | jasperry wrote:
             | You didn't list all functions, just input-output pairs.
             | Each function is a map from every possible input to an
             | output:
             | 
             | f1 False = Nothing, f1 True = Nothing
             | 
             | f2 False = Nothing, f2 True = Just True
             | 
             | ...
             | 
             | This gives the correct 3^2 = 9 functions.
        
       | zem wrote:
       | ocaml is one of my favourite languages too, but I've found myself
       | being drawn towards rust for my latest project due to its major
       | superpower - you can write a rust library that looks like a c
       | library from the outside, and can be called from other languages
       | via their existing c ffi mechanisms. I feel like by writing the
       | library in ocaml I would have a better experience developing it,
       | but be giving up on that free interop.
        
       | pmahoney wrote:
       | I tried to like OCaml for a few years. The things that hold me
       | back the most are niggling things that are largely solved in more
       | "modern" langs, the biggest being the inability to "print"
       | arbitrary objects.
       | 
       | There are ppx things that can automatically derive "to string"
       | functions, but it's a bit of effort to set up, it's not as nice
       | to use as what's available in Rust, and it can't handle things
       | like Set and Map types without extra work, e.g. [1] (from 2021 so
       | situation may have changed).
       | 
       | Compare to golang, where you can just use "%v" and related format
       | strings to print nearly anything with zero effort.
       | 
       | [1] https://discuss.ocaml.org/t/ppx-deriving-implementation-
       | for-...
        
       | vram22 wrote:
       | Is OCaml somewhat suitable for desktop GUI app programming?
       | 
       | I saw this in the OP:
       | 
       | >For example, creating a binding with the Tk library
       | 
       | and had also been thinking about this separately a few days ago,
       | hence the question.
        
       ___________________________________________________________________
       (page generated 2025-08-13 23:00 UTC)