[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)