Merlin vs Ocaml-lsp

I use ocaml-lsp in my $EDITOR. Its generally been a good experience.

I often wonder though… are there any benefits in using merlin directly?

I’m more of modal editor kind of person so I guess emacs is out of the question.

So I guess I would want to compare:

  • Neovim/vim with merlin with
  • Neovim/vim with ocaml-lsp

(I do understand that ocaml-lsp uses merlin under the hood).

If I did use emacs would I gain any more benefits as far as the OCaml integration is concerned?

P.S. I actually don’t use vim but another modal editor called kakoune (with the kak-lsp plugin) but I digress here.

1 Like

I switched from vim+Merlin to neovim+ocaml-lsp and it is basically the same with a bit less hairy setup and overall it works rather well. I mostly only switched because my editor has native LSP integration and I wanted to try it and it worked well enough. It also has the advantage of working like any other LSP as well, thus I can use the same plugins that other people use to interact with their code with OCaml via the LSP interface which is neat.

The only thing I’ve used with Merlin directly that I don’t have at the moment is determining the type of a selection but honestly that was more of a cute feature than a useful one.

After all LSP uses Merlin anyway, so the results are about identical.

2 Likes

There are some features that aren’t possible to expose via the LSP protocol.

That’s the only thing I miss as well. There’s a proposal to enhance LSP to support this but people aren’t caring very much. Not many programming languages have a capable tool like merlin to infer the type of selections :slight_smile:

3 Likes

It’s also helpful to talk about what you will lose.

In my experience the editor frontends that come with merlin perform poorly compared to their LSP counterparts. In Emacs for example, I recall that switching to eglot to ocamllsp gave me a very noticeable boost in completion performance and error checking performance.

I use the type of the expression at the cursor a lot with pure Merlin, and also then extend the selection successively – is that what you mean with type of selection? Or does my use case already work in ocamllsp?

Thanks for the answers!

Ocaml-lsp is definitely very performant compared to a lot of other LSPs I’ve used. The implementation feels responsive and robust. These are two features I would love to have:

  • Type of a selection (I personally haven’t used this but this seems like a very powerful feature). If this is easy to implement as a custom call e.g. ocaml-lsp/selection-type I’ll be happy to integrate it with kak-lsp.
  • Goto definition in libraries. Right now goto definition works well in the main source code but then breaks down in the libraries installed in the switch. In rust-analyzer I’m always able to drill down inside a dependency and that makes rust more understandable to me. I’m always able to goto definition even it is a dependency of a dependency. I was wondering if this an artifact of ocaml-lsp or merlin itself.

BTW I am curious to see the messages being exchanged between merlin and ocaml-lsp.

Is it possible to see those messages flying back and forth? Can I open up merlin in stdin mode and then have ocaml-lsp communicate with it and see the messages in text mode?

3 Likes

It’s not possible because lsp runs merlin in process.

One thing I am missing from merlin is the increase of verbosity (not sure if that’s the right way to name it) when the type of the same value is requested multiple times in a row. It’s not absolutely critical but it was a nice convenient feature.

2 Likes

Has anyone tried Ocaml-LSP with KDE’s Kate editor? I see it now has a LSP client plugin.

1 Like

The LSP protocol needs to be extended so that interaction with the hover windows is allowed. Otherwise I’m not sure how could this be implemented.

Unfortunately I don’t have a good idea except by creating some custom commands and state in the lsp server.

  • The server would have a global value hosting the currently chosen verbosity level
  • One custom command is created to increase the verbosity level (and maybe one to decrease it)
  • Another custom command is created to reset the verbosity level to the default
  • The value returned by the server on a hover command would depend on the global verbosity level currently selected

It’s not exactly as smooth as what is done with merlin, but I think that it could still work.

If the server has a global value, it will be reused between open documents. I think that would be quite unexpected. I’d actually prefer if the verbosity level was managed on the client side. If the protocol doesn’t support this feature, then we might as well give the clients maximum flexibility on how to support this from a UI perspective.

So basically, the client may pass an optional verbosity argument to hover requests. To replicate merlin’s current behavior, the client could have a custom command to repeat the last hover with the verbosity increased by 1. If the client does a hover anywhere else, the verbosity is reset.

1 Like

Surely there must be a way to add an argument to the protocol?

Yes, but you need to update all the clients (which defeats the purpose of the standard lsp clients) to make them submit this new parameter.

I’m really not a fan of letting this to the client. So I tried another way, by having some history in the server. Here is the POC https://github.com/ocaml/ocaml-lsp/pull/561

I wish there was a way in the editor to show generated code from a ppx, potentially optional as not cases make sense. But good cases would show or eq in deriving. Makes it much easier to understand what’s going on, even with just a function signature.

Likely needs some design thought to make it clear it is not part of the on-disk file, and not part of a git commit/diff. Probably more issues. But it would help immensely I think.

You know I used to think about this too – what if we could see see the output of the generated code from the ppx!?

In rust there is a plugin called cargo expand. It shows you the result of all macros expansions in rust. So in some ways its similar to the feature you are requesting in OCaml.

After I ran the cargo plugin I was overwhelmed by the amount of code I saw. Everything is so verbose and de-sugared that it probably only useful if you’re debugging a really really hairy issue. In other words, though the feature exists I’ve rarely used needed to use cargo expand. Most of my macros in rust have been very simple though.

Now, if we did have a ppx expand it probably would be quite difficult to interpret the output. My understanding is that ppx-es work on AST (Abstract Syntax trees) so for this feature to be actually useful we would need to convert the AST back to source code. This trip back to source code would result in source code that might be difficult to understand also. I’m not saying that it can’t be done but its non-trivial which is why we don’t have something like this already.

Today, you can however see the action of the ppx if you’re willing to read AST and there are tool available to see the AST of a source file after is has been preprocessed by a ppx.

P.S. I’m not very familiar with OCaml compiler internals and will be happy to be corrected if anything I’ve said above is not accurate.

There are ways to see the generated code using the -dsource flag, see for example: PPX for end users — ppxlib documentation

So in principle you can replicate cargo expand at least

2 Likes

Yep. I was more interested in ‘beginners’ not remembering what name the show function is.
seeing what signature is, and even an function having a doc string!
Worst case the implementation can be shown, it might be readable with ocamlformat.

… now i’m asking for more work to be done and not helping so i’ll stop :grimacing::grinning_face_with_smiling_eyes:

This is actually a great tip. Thanks @mseri. I tried this out and it definitely works.

Here is an example to make this concrete for people who want more information about -dsource.

Lets say I’m compiling the dream framework.

$ git clone https://github.com/aantron/dream.git
$ cd dream
$ opam install . --deps-only -with-test
$ dune build -p dream
# Lets see the transformation of the src/http/http.ml file
$ cd _build/default/src/http
# ocamlc dumps transformed source on stderr
$ ocamlc -dsource http.pp.ml 2>http.pp.out.ml
# meld is a great difftool. There are many others
$ meld http.ml http.pp.out.ml

A lot of de-sugaring has happened by the time you see http.pp.out.ml

e.g. fun ~a ~b ~c ->... becomes fun ~a -> (fun ~b -> (fun ~c -> ...))) , comments are stripped out etc. So it does become a bit difficult to compare the original with the transformed source.

However the good news is that ppx-es are indeed expanded as advertised.

Something like

let%lwt response = user's_dream_handler request in ...

Now becomes:

 let __ppx_lwt_0 = user's_dream_handler request in
                let module Reraise =
                  struct external reraise : exn -> 'a = "%reraise" end in
                  Lwt.backtrace_bind
                    (fun exn -> try Reraise.reraise exn with | exn -> exn)
                    __ppx_lwt_0
                    (fun response -> ... 

Such feature has been merged into the vscode plugin for ocaml

As an alternative to -dsource, you can do _build/default/.ppx/SOMEHASH/ppx.exe path/to/your/ocaml/file.ml.

Or last solution with merlin

the .merlin file is no longer strictly necessary so dune build @check can be skipped.

3 Likes