This commit is contained in:
swrup 2025-11-11 02:07:51 +01:00
parent aa2ff7b2f0
commit 2f3113f55d
11742 changed files with 1223940 additions and 0 deletions

View file

@ -0,0 +1,165 @@
(* Copyright (C) 2025 Petter A. Urkedal <paurkedal@gmail.com>
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version, with the LGPL-3.0 Linking Exception.
*
* This library is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* and the LGPL-3.0 Linking Exception along with this library. If not, see
* <http://www.gnu.org/licenses/> and <https://spdx.org>, respectively.
*)
(** Reified constructors for product row types.
Usage of this module is somewhat technical and only needed when defining
type descriptors for custom {e parametric} types, or if, for some other
reason, the type descriptor cannot be defined once statically.
For statically defined types, {!Row_type.product} can generate a descriptor
from the bare function.
This module bundles a bare constructor function with a fresh tag from an
open GADT, used to identify it along with its type.
The type may be parametric as long as parameters in the result type occurs
in argument types.
As an example, consider the record type:
{[
type 'a acquired_value = {
source: string;
value: 'a;
confidence: float;
}
]}
A straight forward but not quite correct way to define a type descriptor for
each parametric instance of this type constructor would be:
{[
open Caqti_template
open Caqti_template.Shims
let acquired_value_rowtype value_rowtype =
let open Row_type in
product (fun source value confidence -> Ok {source; value; confidence})
@@ proj string (fun {source; _} -> source)
@@ proj value_rowtype (fun {value; _} -> value)
@@ proj float (fun {confidence; _} -> confidence)
@@ proj_end
let () =
let t1 = acquired_value_rowtype Row_type.int in
let t2 = acquired_value_rowtype Row_type.int in
assert (Row_type.unify t1 t2 = None) (* Bad! *)
]}
The function [acquired_value_rowtype] is, however, generative; a fresh type
descriptor is returned for each call, even for identical arguments.
In particular, this means that {!Row_type.unify} will fail to unify two
descriptors describing the same type, unless they are physically equal.
The correct way of defining this descriptor is to use {!Row_type.product'},
which expects a constructor descriptor instead of a bare constructor
function.
This is the purpose of this module.
To create the custom descriptor, first define a {!type-tag} with correct
signature and a corresponding type-unifying equality predicate:
{[
type (_, _) Constructor.tag +=
Acquired_value : (
string -> 'a -> float -> 'a acquired_value Constructor.return,
'a acquired_value
) Constructor.tag
let acquired_value_constructor =
let tag = Acquired_value in
let unify_tag
: type j b a. (j, b) Constructor.tag ->
(string -> a -> float -> a acquired_value Constructor.return, j)
Constructor.unifier option =
(function
| Acquired_value ->
Some (Constructor.Assume (fun Type.Equal ->
Constructor.Assume (fun Type.Equal ->
Constructor.Assume (fun Type.Equal -> Constructor.Equal))))
| _ -> None)
in
let construct source value confidence = Ok {source; value; confidence} in
{Constructor.tag; unify_tag; construct}
]}
Our original attempt to define the descriptor can now be adjusted to support
parametricity:
{[
let acquired_value_rowtype value_rowtype =
let open Row_type in
product' acquired_value_constructor
@@ proj string (fun {source; _} -> source)
@@ proj value_rowtype (fun {value; _} -> value)
@@ proj float (fun {confidence; _} -> confidence)
@@ proj_end
let () =
let t1 = acquired_value_rowtype Row_type.int in
let t2 = acquired_value_rowtype Row_type.int in
assert (Row_type.unify t1 t2 <> None) (* Good! *)
]} *)
open Shims
type 'a return = ('a, string) result
(** The result type of a constructor function. *)
type ('i, 'j) unifier =
| Equal : ('i return, 'i return) unifier
| Assume : (('a, 'b) Type.eq -> ('i, 'j) unifier) ->
('a -> 'i, 'b -> 'j) unifier (**)
(** [('i, 'j) unifier] witness that two constructors of types [('i, _) t] and
[('j, _) t] are equal, providing a dependent unification of ['i] and ['j] in
the following sense:
- ['i] and ['j] are constrained by this definition to have the shape of a
function which terminates in a {!result} type, like
['a1 -> ... -> 'aN -> 'b return].
- For each constructor argument, a node [Assume f] is provided, where [f]
accepts an equality proof of the constructor argument and returns a
proof of the remaining constructor type.
- A final node [Equal] which, after resolving [Assume] nodes, witness that
constructed values have the same type.
By constraining the return type to a non-abstract non-function type, we
ensure that {!Equal}- and {!Assume}-patterns match disjoint types, so that
we can refute {!Equal} patterns when the type in question is known to be a
function type. *)
type (_, _) tag = ..
(** [('a1 -> ... -> 'aN -> 'r return, 'r) tag] represents the type of a
constructor which takes arguments of type ['a1], ..., ['aN] and returns
values of type ['r].
These tags is normally only passed around in the combination {!type-t}. *)
type ('i, 'a) t = {
tag: ('i, 'a) tag;
(** The constructor type. *)
unify_tag: 'j 'b. ('j, 'b) tag -> ('i, 'j) unifier option;
(** Unifying equality for the constructor type. *)
construct: 'i;
(** The bare constructor function. *)
}
(** [('a1 -> ... -> 'aN -> 'r return, 'r) t] represents a constructor which
takes arguments of type ['a1], ..., ['aN] and constructs values of type
['r]. The public record type is exposed to allow passing the {!unify_tag}
field in a way which preserves universal quantification.
This type is only a reification of the constructor to allow comparison,
disallowed for bare functions, and type unification.
Ideally the {!field-construct} field is unique, while {!field-tag} and
{!field-unify_tag} are implied by the type; the rest is technicalities which
could be handle by a PPX or other kind of code generator. *)
val unify : ('i, 'a) t -> ('j, 'b) t -> ('i, 'j) unifier option
(** [unify t t'] is [Some witness] if [t] and [t'] are equal, otherwise [None].
In the former case, [witness] provides the unification of the result type of
the construction, provided the unifications of each constructor argument
type. *)