Skip to content

Document our extensions #2365

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Merged
merged 1 commit into from
Mar 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions ocaml/jane/doc/include-functor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# The `include functor` extension

The `include functor` extension eliminates a common source of boilerplate when
defining modules that include the results of functors. It adds the module item
form `include functor F`, where `F` must be a functor whose parameter can be
"filled in" with the previous contents of the module. For example, you can now
write this:

```ocaml
module M = struct
type t = ...
[@@deriving compare, sexp]

include functor Comparable.Make
end
```

Traditionally, this would have required defining an inner structure `T` just to
have something the functor can be applied to:

```ocaml
module M = struct
module T = struct
type t = ...
[@@deriving compare, sexp]
end

include T
include Comparable.Make(T)
end
```

These two code fragments behave identically, except that in the first case the
module `M` won't have a submodule `T`.

The feature can also be used in signatures:

```ocaml
module type F = functor (T : sig ... end) -> Comparable.S with type t = T.t

module type S = sig
type t
[@@deriving compare, sexp]

include functor F
end
```

This behaves as if we had written:

```ocaml
module type S = sig
type t
[@@deriving compare, sexp]

include Comparable.S with type t := t
end
```

Currently it's uncommon to define functor module types like `F` (there's no such
module type in `Comparable`, for example). However, you can get the module type
of a functor directly with `module type of`, so the previous signature could
equivalently be written:

```ocaml
module type S = sig
type t
[@@deriving compare, sexp]

include functor module type of Comparable.Make
end
```

## Details and Limitations

This extension is not available in the upstream compiler, so publicly
released code should not use it. We plan to upstream it in the
future.

To include a functor `F`, it must have a module type of the form:

```ocaml
F : S1 -> S2
```

or

```ocaml
F : S1 -> () -> S2
```

where `S1` and `S2` are signatures.

Currently, `include functor` cannot be used in the signatures of recursive
modules. It may be possible to lift this restriction in the future, if there is
sufficient demand.
127 changes: 127 additions & 0 deletions ocaml/jane/doc/labeled-tuples.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# Labeled tuples

The *labeled tuples* extension allows you to label tuple elements. It is
conceptually dual to labeled function arguments, allowing you to give a helpful
name to constructed values where labeled function arguments permit giving a
helpful name to parameters.

Here is a motivating example
where we want to compute two values from a list and be careful
not to mix them up:

```ocaml
let sum_and_product ints =
let init = ~sum:0, ~product:1 in
List.fold_left ints ~init ~f:(fun (~sum, ~product) elem ->
let sum = elem + sum in
let product = elem * product in
~sum, ~product)
```

This example shows the use of labeled tuples in types and patterns. They may be
punned like record elements / function arguments.

In types, tuple labels are written similarly to function argument labels. For
example, the function `f` in the previous example has the type:

```ocaml
(sum:int * product:int) -> int -> sum:int * product:int
```

Labeled tuples are useful anytime you want to use names to explain or
disambiguate the elements of a tuple, but declaring a new record feels too
heavy. As another example, consider this function from `Core_unix` which
creates a pipe with descriptors for reading and writing:

```ocaml
val pipe : ?close_on_exec:bool -> unit -> File_descr.t * File_descr.t
```

Which is which? While it's possible declaring a new record might be best in
this case, we can now use labeled tuples:

```ocaml
val pipe : ?close_on_exec:bool -> unit -> read:File_descr.t * write:File_descr.t
```

Tuples may be partially labeled, which can be useful when some elements of the
tuple share a type and need disambiguation, but others don't. For example:
```ocaml
type min_max_avg = min:int * max:int * float
```

## Reordering and partial patterns

Like records, labeled tuple patterns may be reordered or partial. The compiler
only supports reordering / partial matching when it knows the type of the
pattern from its context.

So, for example, we can write:
```ocaml
# let lt = ~x:0, ~y:42;;
val lt : x:int * y:int = (~x:0, ~y:42)

# let twice_y = let ~y, .. = lt in y * 2;;
val twice_y : int = 84
```

When the type is not known (in the same sense that we require a type to be known
to disambiguate among constructors), the compiler will reject a partial pattern. For
example, this program

```ocaml
let get_y t =
let ~y, .. = t in
y
```

is rejected with this error:

```
File "foo.ml", line 2, characters 8-14:
2 | let ~y, .. = t in
^^^^^^
Error: Could not determine the type of this partial tuple pattern.
```

This example could be fixed by adding a type annotation to the function's
parameter.

Labels may also be repeated in a tuple, and unlabeled elements can be thought of
as all sharing the same unique label. When matching on such a tuple, the first
occurence of a label in the pattern is bound to the first corresponding label in
the value, and so on. As a result, it's also now possible to partially match on
an unlabeled tuple to retrieve the first few elements.

## Limitations

Parentheses are necessary to disambiguate functions types with labeled arguments
from function types with labeled tuple arguments when the first element of the
tuple has a label. `ocamlformat` will handle this for you.

Unlike records, reordering is not supported in labeled tuple expressions, even
when the type is known. This is like how the function definition for a function
with labeled arguments must bind the arguments in the same order as the type.

Labeled tuples do not support projection (extracting an element of the tuple
by label).

Structure-level let bindings do not allow reordering / partial matching as
flexibly as expression-level let bindings. For example, this program does not
typecheck:

```ocaml
module M = struct
let lt = ~x:0, ~y:42
let ~y, .. = lt
end
```

It results in the error:

```
File "foo.ml", line 3, characters 6-12:
3 | let ~y, .. = lt
^^^^^^
Error: Could not determine the type of this partial tuple pattern.
```
25 changes: 25 additions & 0 deletions ocaml/jane/doc/module-strengthening.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# The `module strengthening` extension

What is the type of module `M` in this tiny program?

```ocaml
module type S = sig type t end

module M : S
```

If you said `S` then you are only partially correct: we also need to keep track
of the fact that all type declarations inside it (just `t` in this example) come
from `M`. So the type that the compiler infers is actually stronger:

```ocaml
sig type t = M.t end
```

We call this type "`S` strengthened with `M`". It can be written out explicitly
as above and this is, in fact, the only way to write it without `-extension
module_strengthening`. The new extension allows this type to be written as `S
with M`.

The main motivation for this work are compiler performance improvements, which
use this new form internally.