Is there an empty polymorphic variant type? Since the syntax [>] means a polymorphic variant that can have any constructors, it would seem natural for [<] to mean a polymorphic variant that can have no constructors (hence is necessarily empty), but it doesn’t seem to be accepted. The syntax [] for an “exact” empty polymorphic variant would conflict with empty lists, but I don’t see a problem with [<].
One reason I think this would be useful is that it would be a subtype of all other polymorphic variant types.
I know about empty variants. I’m asking about empty polymorphic variants. Despite its name, the other question seems to be only about empty ordinary variants.
Note also that empty polymorphic variants are supported by the compiler, there is just no syntax for them, partially because they are mostly useless. However, it is perfectly possible to define an empty polymorphic variant ppx. For instance, this test is the compiler testsuite: ocaml/typecore_empty_polyvariant_error.ml at trunk · ocaml/ocaml · GitHub tests an error path that only happens with empty polymorphic variants.
Considering ordinary empty types as a subtype of any type is wrong to me; I’m glad that isn’t the case. But since polymorphic variant types already have subtyping relations, I think it would make sense for them to include an empty version.
I had the same question and opened a feature wish to support [ | ] as the empty polymorphic variant type. If there is positive feedback I may open a Pull Request.
I have just discovered that there do exist polymorphic variant types that are semantically empty even if they are not syntactically empty, such as
type empty = [ `Empty of empty ]
This type has a constructor, but due to the recursion it is not possible to actually construct an element of it, and we can prove that it satisfies the eliminator of the empty type:
let rec abort : type a. empty -> a = function (`Empty e) -> abort e
This doesn’t achieve the general goal of having one polymorphic variant type that is a subtype of every other such. But I think if you have control over the other polymorphic variant types you’re using, you should be able to add the constructor `Empty of empty to all of them, causing them to contain empty as a subtype without adding any actual elements to them. (Although that would require adding an extra `Empty e → abort e clause to any match on them.)
Thank you! You’re right, those modifications work. And for completeness, here is a definition, using that approach, of a family of finite types of all cardinalities that are related by subtyping:
type empty = |
type zero = [ `Empty of empty ]
type 'a suc = [ `Zero | `Suc of 'a | `Empty of empty ]
That’s because in a turing complete language (with full recursion) any type is inhabited, see for instance How many values does a boolean have?, especially the paragraph about Haskell and bottom which is similar to the solution of @viritrilbia. Bottom is the denotation of non terminating computation.
In OCaml, you can inhabit the type empty like this:
type empty = |
let rec make_empty () = (make_empty () : empty)
In other words, in @yallop solution the production-introduction of a bottom value leads to non termination (make_empty ()), whereas in @viritrilbia that’s the use-elimination of such a value that lead to non termination (abort empty) : they are dual point of view.
That was my first reaction too, but I think there’s a difference: your make_empty is a function whose return type is empty, but if you actually call it, it never returns. If you try to define let x = make_empty () then it will compile, but if it will blow up if you run it. Whereas @amongonz 's emptywill actually return: it’s a cyclic data structure, not a divergent computation.
For sure, because OCaml is a call-by-value language. If you look at the blog article I linked, in Haskell, which is a call-by-name language, you could write this equivalent OCaml code:
let rec empty : 'a. a = empty
and that’s the use-elimination of empty that leads to a divergent computation, as in your code example: try abort empty with your implementation and you will have a divergent computation. @yallop and your Haskellish solution are dual : in one case the divergence is in the introduction principle, in the other it’s in the elimination one.
This is not a empty type, since the module could construct
let rec x = `B x
If you want to test that you are defining a provably empty type, you can ask the typechecker directly with an unreachable clause. For instance, this compiles fine:
type empty = |
type t = [ `E of empty ]
let really_empty (x:t) = match x with `E _ -> .
Contrarily, this code
module Empty : sig
type t = private [ `B of t ]
end = struct
type t = [ `B of t ]
let rec x = `B x
end
let not_empty (x:Empty.t) = match (x:> [`B of Empty.t]) with `B _ -> .
fails with
Error: This match case could not be refuted.
Here is an example of a value that would reach it: `B _