Advanced Merlin Features: Destruct and Construct
Principal Software Engineer
Merlin is one of the most important tools for OCaml users, but a lot of its advanced feature often remain unknown. For OCaml newcomers who might not know, Merlin is the server software that provides intelligence to code editors when working on OCaml documents. It allows one to easily navigate the code, get meaningful information (like type information), and perform code generation and refactoring tasks. Merlin installation and usage is documented on its official webpage.
Merlin is distributed with both an Emacs and a Vim plugin. It can also be used in Vscode via the OCaml LSP Server and the corresponding plugin.
In this post, we will focus on two complementary features of Merlin: the venerable destruct
and the younger construct
. Both of these leverage OCaml's precise type information to destruct or create expressions.
Destruct
Destruct (sometimes called case-analysis) uses the type of an identifier to perform multiple tasks related to pattern-matching. It can be called with the following key bindings:
- Emacs: C-d or M-x merlin-destruct
- Vim: :MerlinDestruct
- VSCode: Alt-d or 💡 Destruct
Destruct's behavior changes slightly depending on the context around the cursor. We are going to describe how it behaves in the next three sections.
Automatic Case Analysis
The primary use case for Destruct is to generate a pattern-matching for a given value. Let's consider the following snippet:
let f (x : int option) = x
Calling destruct
on the right-most occurrence of x
will automatically generate the following pattern-matching with the two constructors of x
's' option type:
let f (x : int option) = match x with
| None -> _
| Some _ -> _
What happened is that Merlin looked at the type of x
and generated a complete pattern-matching by enumerating its constructors.
Notice that Merlin used underscores on the right-handsides of the matching. We call these underscores typed holes. These holes are rejected by the compiler, but Merlin will provide type information for them. These holes should not be confused with the wildcard pattern appearing on the left handside Some _
.
After calling destruct
, the cursor should have jumped to the first hole. In
Emacs (resp. Vim), you can navigate between holes by using the commands M-x merlin-next-hole (resp. :MerlinNextHole) and M-x merlin-previous-hole (resp. :MerlinPreviousHole). In VSCode, you can use Alt-y to jump to the next typed hole.
Complete a Matching
Merlin can also add missing branches to an incomplete matching. Given the following snippet:
let f (x : int option) = match x with
| None -> _
Calling destruct
with the cursor on None
will make the pattern-matching
exhaustive:
let f (x : int option) = match x with
| None -> _
| Some _ -> _
Refine the Cases
Finally, Merlin can be used to make a pattern-matching more precise when called on a wildcard pattern _
. Given the following snippet:
let f (x : int option opton) = match x with
| None -> _
| Some _ -> _
Calling destruct
with the cursor on the _
pattern in Some _
will refine the matching:
let f (x : int option option) = match x with
| None -> _
| Some (None) | Some (Some _) -> _
Note that Destruct also works with other types, like records. Let's consider the following snippet:
type t = { a : string option }
let f (x : t) = x
Calling destruct
on the last occurrence of x
will yield:
let f (x : t) = match x with
| { a } -> _
And we can refine it by calling destruct
again on a
, etc.
let f (x : t) = match x with
| { a = None } | { a = Some _ } -> _
That wraps our presentation for destruct
. Generating and completing pattern-
matching cases can be very useful when working with large sum types !
Construct
Construct can be considered as the dual of Destruct, as they work
complementarily. When called over a typed-hole _
, Construct will suggest
values that can fill that hole. It can be called with the following key
bindings:
- Emacs: M-x merlin-construct
- Vim: :MerlinConstruct
- VSCode: Alt-c of 💡 Construct an expression (the cursor must be right after the
_
)
For example, given the following snippet:
let x : int option = _
Calling construct
with the cursor on the _
typed hole will suggest the following constructions:
Some _
None
Choosing the first one will replace the hole and place the cursor on the next hole:
let x : int option = (Some _)
Calling construct
again will suggest 0
and result in:
let x : int option = (Some 0)
In the future, Construct might also suggest fitting values from the local environment instead of solely rely on a type's constructors.
Destruct and Construct
As stated previously calls to destruct
and construct
can be used in
collaboration. For example, after calling destruct
on x
in the following code snippet:
type t = { a : unit; b : string option }
let f (x : int option) : t option = x
x
is replaced by a matching on x
with the cursor on the first hole:
let f (x : int option) : t option =
match w with
| None -> _
| Some _ -> _
One can immediately call construct
and choose a construction for the first branch:
let f (x : int option) : t option =
match w with
| None -> None
| Some _ -> _
And again for the second branch:
let f (x : int option) : t option =
match w with
| None -> None
| Some _ -> Some _
Finally, like Destruct, Construct also works with records and most OCaml types:
Some _ → Some { a = _; b = _ } → Some { a = (); b = None }
Conclusion
When put to good use, these complementary features can remove some of the burden of working with big variant types. We encourage you to try them and see if they help your everyday workflow! If you encounter any issues or have ideas for improvement, please communicate them to us via the issue tracker.
Open-Source Development
Tarides champions open-source development. We create and maintain key features of the OCaml language in collaboration with the OCaml community. To learn more about how you can support our open-source work, discover our page on GitHub.
Stay Updated on OCaml and MirageOS!
Subscribe to our mailing list to receive the latest news from Tarides.
By signing up, you agree to receive emails from Tarides. You can unsubscribe at any time.