Tuareg and Caml modes for Emacs: what are the differences?

I know that Tuareg mode and Caml Mode are two Emacs modes for OCaml. But what are the differences between them? Are they alternatives, or to be used together?

All I can find is Tuareg is described as a GOOD Emacs mode to edit Objective Caml code. and Caml-mode is described as another mode for ObjectiveCaml. I’d like to know why one is considered “good” and the other is just “another”, and how widely this evaluation (this opinion) is shared.

I think tuareg-mode might be the more popular package when editing OCaml in Emacs; w.r.t the exact difference in features, I think others on the forum might be more qualified to comment.

You may also want to check out ocp-indent and merlin-mode.

1 Like

I am using tuareg-mode along with utop, merlin and flymake-ocaml in my Emacs setup. They depend on the merlin opam package to be installed in your opam switch, and it works wonderfully. My emacs setup is a vanilla checkout of emacs-prelude with a few small adjustments to keybindings in merlin mode.

I have tried the ocaml lsp integration using both eglot and lsp-mode but I have not found a configuration that I am happy with.

Here is a snippet of the elisp I use to configure OCaml on my Emacs:

(require 'cl)
(require 'dash)

(defun init-ocaml/check-opam-installed (package)
  "Check whether a package is installed in opam.
  PACKAGE is a string containing the package"
  (let ((cmd
	 (format
	  "(opam list --installed %s 2>&1 | tail -n 1 | grep -q -v \"^#\" )"
	  package)))
    (= (shell-command cmd ) 0)))


(let ((site-file (replace-regexp-in-string
		  "\n" "/share/emacs/site-lisp/tuareg-site-file"
		  (shell-command-to-string "opam var prefix"))))
  (load site-file))

(use-package tuareg
  :ensure nil
  :config
  (add-hook 'tuareg-mode-hook #'(lambda () (setq mode-name "🐫")))
  
  (bind-key (kbd "C-c C-h") #'merlin-document tuareg-mode-map)

  (when (and nil (init-ocaml/check-opam-installed "ocamlformat"))
    (use-package ocamlformat
      :ensure nil
      :config
      (define-key tuareg-mode-map (kbd "C-M-<tab>") #'ocamlformat)))

  (when (init-ocaml/check-opam-installed "merlin")
    ;; Merlin configuration
    (use-package merlin
      :ensure nil
      :config
      (add-hook 'tuareg-mode-hook #'merlin-mode)
      (add-hook 'merlin-mode-hook #'company-mode)


      (setq merlin-error-after-save t)
	 
      (define-key tuareg-mode-map (kbd "C-c C-f") #'merlin-search)
      (define-key tuareg-mode-map (kbd "C-c C-v") #'merlin-occurrences)
      ))

  (when (init-ocaml/check-opam-installed "ocp-indent")
    (add-to-list 'load-path (replace-regexp-in-string
			     "\n" "/share/emacs/site-lisp"
			     (shell-command-to-string "opam var prefix")))
    (use-package ocp-indent
      :ensure nil
      :config
      (add-hook 'tuareg-mode-hook
		(lambda ()
                  (setq ocp-indent-path "ocp-indent")
		  (add-hook 'before-save
                            'ocp-indent-buffer nil 'local)))))
  
  (when (init-ocaml/check-opam-installed "utop")
    (add-to-list
     'load-path
     (replace-regexp-in-string
      "\n" "/share/emacs/site-lisp"
      (shell-command-to-string "opam var prefix")))

    ;; utop configuration
    (use-package utop
      :ensure nil
      :init
      (setq utop-command "opam config exec -- dune utop .. -- -emacs")
      (setq utop-skip-after-eval-phrase nil)
      :config
      (autoload 'utop-minor-mode "utop" "Minor mode for utop" t)
      (add-hook 'tuareg-mode-hook 'utop-minor-mode)))

  
  (defun init-ocaml/open-dune-file ()
    "Opens the nearest enclosing dune-file."
    (interactive)
    (find-file (let ((path-components (-filter (lambda (s) (not (string-empty-p s)))
                                               (split-string (file-name-directory (buffer-file-name)) "/")))
                     dune-file)
                 (while (and (not dune-file) (> (length path-components) 1))
                   (let ((path
                          (string-join (list "/" (string-join path-components "/") "/"))))
                     (setq dune-file (seq-find (lambda (s) (string-equal s "dune")) (directory-files ".")))
                     (unless dune-file
                       (setq path-components (-drop-last 1 path-components)))))
                 (when dune-file
                   (setq dune-file
                         (string-join (list "/" (string-join path-components "/") "/" dune-file))))
                 dune-file)))
  (bind-key (kbd "C-c C-q") #'init-ocaml/open-dune-file tuareg-mode-map))

(provide 'init-ocaml)
2 Likes

Only opam user-setup insall

1 Like

Thanks for all of you taking time to reply. I think there might be some misunderstanding: what I am concerned about is the difference in features between tuareg and caml-mode, not any questions about how to configure either of them.

I am a raw beginner at OCaml. For the time being, I have used opam to install and configure merlin as suggested in Get Up and Running with OCaml, and also (not covered there) used opam to install and configure tuareg. It seems to be working well.

A little earlier I had caml-mode but did not use it extensively. My crude impression is that editing with tuareg, the code looks prettier, but I could not tell what other differences there were. So if anyone with deeper knowledge of both tuareg and caml-mode wants to chime in, I will be eager to listen. If not, I will just continue with the setup I have for a while! :upside_down_face:

I just noticed that the next step of Quickstart OCaml (whence I came to “Get Up and Running with OCaml”) does cover editor setup, including tuareg.

I’m using caml-mode but AFAIR that’s just because I find its code highlighting more pleasant (though it needs a tweak for the ocamldoc comments).

Except for switching between .mli and .ml files, the rest nowadays seems to be entirely provided by merlin and ocp-indent.

(Also I’m a basic user I don’t use fancy stuff like form insertions etc.)

1 Like

From what I gathered caml-mode was the original mode (born in 1993) and tuareg-mode is a newer mode (1997) with more features than caml-mode. It features a much more sophisticated indentation engine, based on SMIE, and is maintained by one of Emacs’s own maintainer (Stephan Monnier). Overall their feature-set is similar and actually tuareg even depends on caml-mode for some features, although they seem obsolete to me these days. With merlin-mode doing most of the heavy lifting I guess for most people the result of using both modes would be the same.

I was left with the impression that Tuareg is the more popular mode and it’s the one I’ve been using myself.

P.S. The EmacsWiki is very outdated on many topics, so I wouldn’t fret too much on things I’ve read there.

3 Likes

Btw, I’m Emacs Prelude’s author. :slight_smile: I’ve tried using OCaml with Eglot, but I was running into some issues with it, so I’ve opted the for the classic Merlin setup instead. It already provided everything I needed, so I didn’t see much value in pushing to use OCaml-LSP (which still uses Merlin anyways).

4 Likes

Thank you very much for Emacs Prelude :grinning:

OCaml-LSP in Emacs is quite viable, I spent the weekend debugging my setup and getting it just right. The tricky part is getting Emacs to pickup the correct path based on the opam switch, then once that is setup there is very little lisp config. Using direnv (there could be a better elisp way) and the following configuration in ~/.emacs.d/personal/tsmc.el

(prelude-require-packages '(use-package direnv))
;; Use direnv to select the correct opam switch and set the path
;; that Emacs will use to run commands like ocamllsp, merlin or dune build.

(use-package lsp-mode
  :hook
  (tuareg-mode . lsp))
;; Attach lsp hook to modes that require it, here we bind to tuareg-mode rather than
;; prelude-ocaml. For unknown reasons the latter does not bind properly and does not
;; start lsp-mode

I wrote up a longer form version of my setup at OCaml with Emacs in 2022 · Perpetually Curious Blog There are still some bits I am not happy with but I have been using it daily.
Also @bbatsov wrote his version at Setting up Emacs for OCaml Development - (think)

1 Like

Could you maybe expand on the advice not to use brew’s emacs ?

I think I went the other direction, that is using emacsonosx to using brew install emacs’s without seing a difference (except for the convenience of the latter if you already use brew). Unless I’m mistaken I does install the GUI version and an application in /Application.

This PR#36070 from 2019 removed with --with-cocoa option resulting in no GUI version being installed in Emacs. Perhaps this has changed back or been improved since then I have not checked. At that time I switched to the emacsformacos built version.

This discussion mentions using cask instead. At some point it seems they stopped doing the discinction between casks and regular formulae. I think you should try to do a simple brew install emacs and you’ll likely get what you want.

Homebrew provides both a terminal emacs and the GUI Emacs. With brew install emacs giving you the terminal version and brew install --cask emacs downloading the emacsformacosx version. I missed the distinction initially but it is nice both are available.

I will edit my post to clarify the point and suggest installing Emacs via homebrew since you usually have that installed if you are using macOS for development purposes.

1 Like

Recent comments re. Homebrew and Emacs configuration have drifted far from my question, so I will be unwatching this topic (so feel free to talk about those issues all you like). :slightly_smiling_face:

If, however, anyone wants to tell me more about the differences between tuareg and ocaml-mode, please do reply or mention me ( @gdw ).