xvw.lol - Guarded methods in OCaml
Guarded methods in OCaml
2025-06-29
This article is a translation, the original version is<br>available here.
Guarded methods allow attaching constraints to the receiver (self) only for certain methods , thus allowing these methods to be called only if the receiver satisfies these constraints (these guards). OCaml does not syntactically allow defining this kind of method directly. In this note, we will see how to encode them using a type equality witness .
Problem presentationMoving the method outside the class<br>Extension methods<br>Guarded methods<br>OOP/FP symmetry: theory and practice
Guarded methods in OCamlProvide a type equality witness<br>Constrain with eqImplementing the interface obj_list<br>Adding a guarded method sum
To conclude
Guarded methods make it possible to attach constraints to the<br>receiver (self) only for certain methods , meaning these methods<br>can only be called if the receiver satisfies those constraints (these<br>guards). OCaml does not, syntactically, allow<br>defining this kind of method directly. In this note, we’ll look at<br>how to encode them using a type equality witness .
Problem presentation
When a language (where type checking is done before the program runs,<br>like in Java or OCaml) introduces parametric polymorphism (Java's<br>generics), it's sometimes possible to constrain type variables. For<br>example:
class MyClassextends S> { ... }
We make MyClass generic by assuming that the type variable T is a<br>subtype of S. The problem is that the constraint applies to the<br>entire class . Yet sometimes, we’d like to have constraints apply<br>only to certain methods . For example, let’s say we have a class<br>MyList describing a list:
class MyList extends ArrayListA> {<br>public int length() {<br>return this.size();
How can we define a flatten method that, for a list like [[1, 2, 3], [4, 5]], would produce the list [1, 2, 3, 4, 5]? If we place<br>the constraint at the class level, we force our list to be "always a<br>list of lists," which is very limiting. To implement such a method,<br>we have three theoretical approaches available.
Moving the method outside the class
The first solution is the most obvious: simply "cheat" by moving the<br>method outside the class body (for example, into the static context or<br>a companion object):
class MyList extends ArrayListA> {<br>public static A> MyListA> flatten(MyListMyListA>> list) {<br>// Flatten's implementation<br>public int length() {<br>return this.size();
This approach works and doesn’t require any special ceremony. However,<br>it forces the developer to keep track of which methods are in the<br>class body and which are in the static context. Moreover, it breaks<br>the systematic approach of sending messages to an instance (often<br>presented as one of the key arguments in favor of object-oriented<br>programming).
Extension methods
Kotlin (and others, like<br>C#) offer<br>extension methods<br>which, in addition to allowing the extension of an already existing<br>class (which can be very useful for adding behavior to the String<br>class, which is final in Java), also provide more flexibility in<br>defining the receiver. For example, we could write flatten like<br>this (in Kotlin):
class MyListA> : ArrayListA> { ... }<br>fun A> MyList>.flatten() = ...
Even though this solution seems nearly perfect, it still requires the<br>method to be defined outside the class , which might potentially<br>mean having to make certain members of the class public in order to<br>be accessible from an extension (looks like potentials leaky<br>abstractions). However, it still preserves the systematic<br>message-sending approach while allowing for more fine-grained<br>qualification of the receiver .
Guarded methods
The final approach is probably the most ideological, as it keeps the<br>method definition within the class. It doesn't force any escaped<br>abstractions. This is the idea of guarded methods , the ability to<br>add constraints on the generic parameter at the method definition<br>level. In an imaginary syntax (this code compiles because it's not<br>syntactically invalid, but it doesn’t produce the intended effect):
class MyListA> : ArrayListA>() {<br>fun length() = size<br>fun B> MyList>.flatten() =<br>// Implémentation de flatten
Even though this doesn’t seem significantly different from classic<br>extension methods (as evidenced by their syntax, which inside the<br>class body seems sufficient), it addresses all the issues raised<br>earlier:
we can characterize the receiver more precisely than in a normal method
we don’t break the regular message sending
we still benefit from available members (so we don’t escape<br>representations)
Although guarded methods seem necessary, unfortunately, I don’t know<br>of any mainstream languages that allow their definition. That’s very<br>sad. Fortunately, in OCaml, it is possible to encode them.
OOP/FP symmetry: theory and practice
Since guarded methods are quite rare in popular programming languages,<br>I discovered their existence fairly recently while reading the<br>slides<br>from the presentation "The...