How does ocaml compare to F# in the family of ml languages

Thank you for replying.

I don’t know why but when I came across Dream I thought it was not production ready.

When you say

are you suggesting me to convert Ocaml code to JavaScript and use Cordova ?

Is dmDart a typo? Did you meant the Dart language? I searched by dmDart Ocaml and I got nothing.

There are so many technical choices possible in what you’ve listed that it’s difficult to say what production ready even means…

I will just list a few things:

  • BeSport is implemented in OCaml using Ocsigen (I believe their mobile apps are implemented the same way)
  • is implemented using Dream
  • Ahrefs use ocaml and melange for their website.

All these are in production with a none negligible user base.


dmDart was a typo, sorry about the confusion. The Flutter seems to me one of the few viable platforms designed to do exactly what you are looking for regarding the multiple front ends.

are you suggesting me to convert Ocaml code to JavaScript and use Cordova ?

That has been the general approach I’ve seen, I know that’s what Ocsigen does.

Dune supports cross-toolchains but I think that’s only viable for sharing some embedded logic. I doubt building an entire app with it would be reasonable even if it’s possible.

1 Like

On the other hand if you have enough first hand experience it shouldn’t be too hard to see that there’s enough in the eco-system at this point to collect a bunch of existing packages into something that will get you what, say flask or expressjs provide. This can already get you a long way for many projects (assuming these absolutely terrible systems are the “production ready” one really wants to work with :joy:).

The difference between what people think they need and what they actually need is often quite large, especially in an industry ridden by attention deficit disorder, advertisement and cargo culting.

I don’t think OCaml will ever be mainstream, but I do think it is production ready: it all depends on what you want to produce :–)

But certainly, rather than sift through SO answers to find code to cut and paste to work around the many defects of mainstream systems you might need to invest a bit more in DIY, which depending on your perspective, you can see as a chore (gets in my way) or a blessing (makes me a better programmer).


I’m not saying OCaml is Rails or Spring boot, and I don’t think anybody on this forum would pretend otherwise. But that question doesn’t even need to be asked :wink:
My answer was in the context of the question: I’m familliar with Python, the natural way to write an ios is Swift, but I want to learn and I’m exploring other languages…
So yes, in that context, I consider OCaml ready enough.

Secondly there is a chicken and egg situation here, where will the ecosystem come from if nobody is building using OCaml? E.g. why would anybody write and aws SDK if nobody is doing OCaml on AWS?
Now, should that pioneer be you or your company? That’s a important question to answer for yourself, but successful companies have been built on less solid foundations.


sounds like circular reasoning to me.

I appreciate all your comments and points of view.

I think that for now it’s better for me to develop the product I have in mind using the tools I’m used to and learn more about Ocaml prior trying to use it for $omething that involve$ money.

Thank you all.

1 Like

F#'s open-source VS Code tooling (Ionide and related projects) is simply not good, significantly worse than OCaml Platform. I’m sorry because I hope the best for the F# community, but the tooling breaks, crashes and/or hangs every other Ionide release, every other .NET/compiler release or on every other complex project, and their formatter is dangerously unsound and has deleted entire files of mine in the past (thanks, undo). Not to mention half the features that don’t work[1].

The propietary editors, however, are much better if you can handle dealing with full blown IDEs. Rider is a pleasure to use with F#, and Visual Studio… I don’t personally like it, but it works; both make it harder to work with more complicated MSBuild projects, though.

But none of this takes in consideration that the F# compiler toolchain itself is slow and falling behind the rest of the .NET ecosystem[2]. It earned a good reputation when every other functional language was doing really badly in terms of tooling, but those times are gone. I’d like to push some of the blame on the C# and CLR teams, which only seem to care about C#'s needs[3], but even then F# goes for years without fixing the obvious edge cases or compatibility needs, and it’s reaching a point where you just can’t consume the most modern C# code anymore.

Sorry if this comes a bit like a venting post, lol. There’s a reason I use OCaml now for my own stuff (or C# if I need .NET).

  1. For example, I’ve never managed to reorder F# files in the editor (file order matters to the F# compiler), despite there being buttons for it; they just didn’t do anything. Refactors were also often broken, but so are OCaml Platform’s, at least when PPXs are involved, so that’s a draw I guess. ↩︎

  2. For example, byref-like types still don’t show in autocompletion because they can’t unify with type variables. Which also means raise : exn -> 'a can’t be used if 'a unifies with one of those. ↩︎

  3. I would know because I’ve both tried (and failed) to improve the F# compiler, as well as written my own for a custom .NET programming language. So many features and conventions are being added to the CLR and CIL without a plan for how will other languages consume them, and so many restrictions only ever revised when C# needs to, than it should be called the C# Language Runtime at this point. ↩︎


Good to know. I would pay lots of $ for an IntelliJ / OCaml setup as good as IntelliJ / { Rust, Scala, Kotlin, Java }.

Thanks for sharing this. I never managed to get dotnet/mono/f# setup properly on NixOS, and was suspecting the toolchain is not that great to use.

I was coding in Rust just now, and hit:

match blah {

then I put the cursor under match, hit fill, and it (1) got the type of blah correctly, (2) filled in all arms of the enum

In neovim/OCaml, this is a (1) manually get type of blah, (2) jump to teh source loc of the type, (3) copy paste the arms of the enum.


This works for me in VSCode OCaml Platform, except it’s a bit unreliable: mostly for the obvious reason it needs to know the type (e.g. sometimes there isn’t enough text to make the syntax-with-typed-holes compile even though the intended type would be obvious to a reader).


Thanks for letting me know. I may have to try VSCode OCaml again just for this.

I started learning F# a few days ago (due to Ocaml), so I don’t have too much experience with it, but so far it has been a pleasure to develop with VSCode + Ionide.

In fact, I find it much easier to use the F# tools than the ones available for Ocaml.

I’m using it on Debian and on Open SUSE and I had no problem installing it (truth be told, installing Ocaml is a breeze).

This comparison is somewhat surprising to me, because that particular workflow is something I use in OCaml all the time (select variable, auto-generate match cases, etc.).

In merlin in Emacs this can be done with C-c C-d (merlin-destruct), and provided that the type of the variable is known (which it will be if you’re writing OCaml in a convention where the types of all function arguments are explicitly given (a la rust)), merlin will autogenerate the cases.

1 Like

Yeah, it looks like I’m in the wrong here; i.e. it is a limitation of my configs, not of OCaml itself.

And desctruct is also available in neovim as one of the action offered by ocamllsp
Screenshot 2023-08-03 at 09.15.13

1 Like

Would you mind posting your neovim/ocaml config to github ? (I’m looking for parts to plagiarize).


I’m currently using [attached] and trying to figure out what else to add:

local on_attach = function(client, bufnr)
    -- enable completion triggered by <C-x><C-o>
    vim.api.nvim_buf_set_option(bufnr, "omnifunc", "v:lua.vim.lsp.omnifunc")

    local bufopts = { noremap=true, silent=true, buffer=bufnr }
    vim.keymap.set("n", "gD", vim.lsp.buf.declaration, bufopts)
    vim.keymap.set("n", "gd", vim.lsp.buf.definition, bufopts)
    vim.keymap.set("n", "K", vim.lsp.buf.hover, bufopts)
    vim.keymap.set("n", "gi", vim.lsp.buf.implementation, bufopts)
    vim.keymap.set("n", "gk", vim.lsp.buf.signature_help, bufopts)
    vim.keymap.set("n", "<space>wa", vim.lsp.buf.add_workspace_folder, bufopts)
    vim.keymap.set("n", "<space>wr", vim.lsp.buf.remove_workspace_folder, bufopts)
    vim.keymap.set("n", "<space>wl", function()
    end, bufopts)
    vim.keymap.set("n", "<space>td", vim.lsp.buf.type_definition, bufopts)
    vim.keymap.set("n", "<space>rn", vim.lsp.buf.rename, bufopts)
    vim.keymap.set("n", "<space>ca", vim.lsp.buf.code_action, bufopts)
    vim.keymap.set("n", "gr", vim.lsp.buf.references, bufopts)
    vim.keymap.set("n", "<space>f", vim.lsp.buf.formatting, bufopts)
    vim.keymap.set("n", "<space>do", vim.diagnostic.open_float, bufopts)
    vim.keymap.set("n", "gp", vim.diagnostic.goto_prev, bufopts)
    vim.keymap.set("n", "gl", vim.diagnostic.goto_next, bufopts)
    vim.keymap.set("n", "<space>dl", vim.diagnostic.setloclist, bufopts)

    -- format on save
    if client.server_capabilities.documentFormattingProvider then
        vim.api.nvim_create_autocmd("BufWritePre", {
            group = vim.api.nvim_create_augroup("Format", { clear = true }),
            buffer = bufnr,
            callback = function() vim.lsp.buf.formatting_seq_sync() end
        vim.api.nvim_create_autocmd("BufWritePre", {
            group = vim.api.nvim_create_augroup("Format", { clear = true }),
            buffer = bufnr,
            callback = function() vim.lsp.buf.formatting_seq_sync() end
    -- code lens 
    if client.resolved_capabilities.code_lens then
        local codelens = vim.api.nvim_create_augroup(
            { clear = true }
        vim.api.nvim_create_autocmd({ 'BufEnter', 'InsertLeave', 'CursorHold' }, {
            group = codelens,
            callback = function()
            buffer = bufnr,

    cmd = { "ocamllsp" },
    filetypes = { "ocaml", "ocaml.menhir", "ocaml.interface", "ocaml.ocamllex", "reason", "dune" },
    root_dir = lsp.util.root_pattern("*.opam", "esy.json", "package.json", ".git", "dune-project", "dune-workspace"),
    on_attach = on_attach,
    -- capabilities = capabilities

I think we had that conversation before, I use a config distribution (astronvim) so I don’t really have any specific for ocaml in my set-up (well almost, ocaml-lsp and Mason don’t play well together).
So I’m afraid I have nothing to share.

In this case, it seems that you already have the required mapping: vim.keymap.set("n", "<space>ca", vim.lsp.buf.code_action, bufopts)

Maybe the difference with what you are doing in rust, ocaml-lsp expect to be invoked when the cursor is on bla not inside the match ... with branches. Also it will generate the match with for you, so my usual flow will be to type let foo_bar = blah ... move back to blah and call destruct on it.

1 Like

I think you’re right.

It definitely works for me now. Thanks! Strange I never got used to this.