Library

Module

Module type

Parameter

Class

Class type

sectionYPositions = computeSectionYPositions($el), 10)" x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)">

On This Page

Legend:

Library

Module

Module type

Parameter

Class

Class type

Library

Module

Module type

Parameter

Class

Class type

The aim of this library is to provide simple access to user-defined types. The domain of optics for functional languages (and its documentation) can be **very** vast. In this respect, this library is **not** intended to (re)implement all this documentation with increasingly complicated types, but rather to offer a fairly "straightforward" user experience (symptomatic of OCaml developers) to solve fairly simple access and mutation problems on records and ADTs.

This module offers two views: lenses and prisms. The first is used for records, the second for ADTs. These views offer values of a certain type `t`

which can subsequently be composed together. These values describe your type and they can be self-generated by a **ppx**.

From these values, we can use the `get`

function or the `set`

function (or `setf`

). In this way, accessors can be obtained from a description of the user's types.

Let's take the example of a "nested" record.

```
type t = { c : string }
type s = { a : int; b : t }
```

It may be difficult to update the `c`

field from a value of type `s`

. Indeed, we might normally write something like this:

```
let update_c s v =
{ s with b= { s.t with c = v } }
```

This style becomes more unwiedly as the level of nesting increases. Lun(ette) solves this problem by allowing you to write this instead:

```
let b () = Lun.lense (fun { b; _ } -> b) (fun s b -> { s with b })
let c () = Lun.lense (fun { c } -> c) (fun t c -> { t with c })
let update_c s v = Lun.(set (b >> c) v s)
```

The values `b`

and `c`

can be reused and combined with other values such as:

```
type r = { d : s; e : float }
let d () = Lun.lense (fun { d; _ } -> d) (fun r d -> { r with d })
let update_c' r v = Lun.(set (d >> c) v s)
```

The purpose here is mainly to use values in order to get (`get`

) or modify (`set`

) fields in a record which can then be composed. For nested and more complex records, such a library can really facilitate their handling.

As you can see, the creation of a lense is quite simple and can be automated. That is why Lun(ette) also offers a **ppx** that automatically generates these values.

The optics presented here as well as those generated by our ppx do **not** mutate, even on fields annotated as mutable. The use of `set`/`setf` remains "referentially transparent":

```
type t = { mutable v : int }
let v () = Lun.lense (fun { v } -> v) (fun _ v -> { v })
let v0 = { v=0 }
let v1 = Lun.setf v ~f:succ v0
let () =
assert (v0.v = 0);
assert (v1.v = 1);
v1.v <- 2;
assert (v0.v = 0);
assert (v1.v = 2)
```

Previously, we have seen the lenses that characterise the *"has-a"* relationship. Our lense `c`

characterises that our type `s`

**has** the field `c`

.

The prism characterises another relationship: the *"is-a"* relationship.

In this sense, the `get`

operation allows us to know if a value corresponds to our focus (and potentially returns its associated value) or not. Let's take an example:

```
type t =
| A of int
| B
| C of string
let a () = Lun.prism (fun v -> A v) @@ function
| A v -> Result.ok v
| x -> Result.error x
let is_a : t -> int option = Lun.get_opt a
assert (is_a (A 42) = Some 42)
assert (is_a B = None)
```

It should always be kept in mind that these values are composable, thus:

```
type t = { name : string option }
let name () = Lun.lense (fun { name } -> name) (fun _ name -> { name })
let update_name : string -> t -> t = Lun.(set (a >> some))
assert (update_name "Romain" { name= None } = { name= None })
assert (update_name "Romain" { name= Some "Patrick" } = { name= Some "Romain" })
```

As you may know, parentheses have a syntactic meaning with respect to constructors. `Foo of (int * int) != Foo of int * int`

. This difference is not only syntactic, but also has an impact on the representation of our `Foo`

constructor. However, this difference may have an impact on the very definition of our prisms:

```
type t =
| Foo of int * int
| Bar of (int * int)
let foo () = Lun.prism (fun (a, b) -> Foo (a, b)) @@ function
| Foo (a, b) -> Ok (a, b)
| x -> Error x
let bar () = Lun.prism (fun v -> Bar v) @@ function
| Bar v -> Ok v
| x -> Error x
```

`ppx_lun`

.Lun(ette) offers a ppx that can generate the values seen above. The ppx manages records, ADTs (with or without a tuple/record). It does not, however, handle GADTs.

Its use is relatively simple, it consists in annotating a type with `[@@deriving lun]`

.

```
type t = ..
[@@deriving lun]
```

The ppx will then generate values whose name is the name of the type suffixed with the name of the field (in the case of a record) or the constructor (in the case of an ADT).

```
type a = A
val a_A : ...
type b = { b : a }
val b_b : ...
```

For more details on the generation, the distribution offers a lun executable that allows to obtain the ppx result:

```
$ opam install ppx_lun
$ cat >main.ml <<EOF
type t = { v : int } [@@deriving lun]
EOF
$ lun main.ml
type t = {
v: int }[@@deriving lun]
include
struct
let _ = fun (_ : t) -> ()
let t_v () =
Lun.lense (fun vObwn1M -> vObwn1M.v) (fun _ -> fun v -> { v })
let _ = t_v
end[@@ocaml.doc "@inline"
```

@@merlin.hide ]

The first limitation is the OCaml *value restriction* which forces us to *eta-expand* our value with `fun () -> ...`

. In this case, this library is focused on using `t`

which is the eta-expansion of `s`

. So you should not manipulate `s`

in our program.

For more details on this subject, we advise you to read: Polymorphism and its limitations.

However, thanks to this *eta-expansion*, we are able to manipulate parametric types like `'a option`

or `'a list`

:

```
let hd () = Lun.prism (fun v -> [ v ]) @@ function
| v :: _ -> Ok v
| v -> Error v
```

Of course, accessing a field via Mon(ette) will be slower than its handwritten counterpart (this is the price of composability).

A Lun(ette) access can be 15 to 20 times slower than a handwritten access. However, as you can imagine, the goal of Lun(ette) is not performance but ease of handling and composing optics.

One of the most seen aspects of optics is the composability and reusability of optics with generalized functions (such as `map`

or `fold`

). This reuse can look very good (especially in Haskell) but it is a departure from a certain vision of what is usually done in OCaml.

Of course, this remains a matter of opinion, but if the expressiveness of Lun(ette) can be criticised (compared to what may already exist in terms of "accessors" in OCaml), the real reason remains in our ambition to offer a fairly straightforward library to use (without requiring a CS degree).

The original optic representation similar to the standard Van Laarhoven encoding.

`type (-'s, +'t, +'a, -'b) t = unit -> ('s, 't, 'a, 'b) s`

Type of optics where the type parameters are as follows:

`'s`

the source type`'t`

the output type`'a`

the focus of the lense`'b`

is the type such as`'b/'a`

's = 't, i.e. the result type of the focused transformation that produces the necessary output`'t`

.

**NOTE**: the `unit`

type is due to *the value restriction*. We must *eta-expand* values to let the compiler to generalize correctly polymorphic values.

`val lense : ('s -> 'a) -> ('s -> 'b -> 't) -> ('s, 't, 'a, 'b) s`

`lense prj inj`

makes a new optic which *projects* `'a`

from `'s`

and *injects* `'b`

into `'t`

from `'s`

. For instance:

```
type t = { v : int }
let optic () = Lun.lense (fun { v } -> v) (fun _ v -> { v })
```

`val prism : ('b -> 't) -> ('s -> ('a, 't) Stdlib.result) -> ('s, 't, 'a, 'b) s`

`prism inj prj`

makes a new optic which *injects* a value `'b`

as a `'t`

and **tries** to *project* `'a`

from `'s`

. For instance:

```
type t = A of int | B of string
let optic () = Lun.prism (fun n -> A n) @@ function
| A n -> Ok n
| v -> Error v
```

An exception raised by `get`

when it's not possible to project a value with an optic.

`val get : ('s, 't, 'a, 'b) t -> 's -> 'a`

`get optic s`

tries to *project* `'a`

from `'s`

. If the given `optic`

is a `lense`

, this function **never** fails. Otherwise, if the given `optic`

is a `prism`

, it can raise an exception `Undefined`

. For instance:

```
type t = A | B
let a () = prism (fun () -> A) @@ function
| A -> Ok ()
| x -> Error x
let () = match Lun.get a B with
| () -> print_endline "Unexpected A value"
| exception Lun.Undefined -> print_endline "The given value is not A"
```

`val get_opt : ('s, 't, 'a, 'b) t -> 's -> 'a option`

`get_opt optic v`

tries to *project* `'a`

from `'s`

. If it's not possible (see `get`

for more details), it returns `None`

. Otherwise, it returns the value `'a`

with `Some`

.

`val set : ('s, 't, 'a, 'b) t -> 'b -> 's -> 't`

`set optic v t`

tries to inject from the given `optic`

the value `v : 'b`

into the given value `t : 's`

and return it as a `'t`

value. For instance, below, `set_v`

and `set_v'`

are equivalent:

```
type t = { v : int }
let optic = Lun.lense (fun { v } -> v) (fun _ v -> { v })
let set_v v t = Lun.set optic v t
let set_v' v _ = { v }
```

In the case of a `prism`

, `t`

is only changed if the constructor is the one described by the given `optic`

. For instance:

```
type t = A of int | B of string
let a () prism (fun v -> A v) @@ function
| A v -> Ok v
| x -> Error x
assert (set a (A 42) (B "Hello World") = B "Hello World")
```

`val setf : ('s, 't, 'a, 'b) t -> f:('a -> 'b) -> 's -> 't`

`setf optic ~f s`

tries to update the value `'a`

to `'b`

from `'s`

and returns `'t`

. For instance:

```
type t = A of int | B of string
let a () = prism (fun v -> A v) @@ function
| A n -> Ok n
| v -> Error v
let succ = Lun.setf a ~f:succ
assert (succ (A 0) = A 1)
```

`a >> b`

composes the given optic `a`

with `b`

such as `b`

will inject what `a`

projects and *vice-versa*. For instance:

```
{
type t = A of int option | B of string
let a = Lun.prism (fun n -> A n) @@ function
| A n -> Ok n
| v -> Error v
let succ = Lun.(setf (a >> some) ~f:succ)
assert (succ (A (Some 0)) = A (Some 1))
}
```

Common lenses and prisms.

`val fst : ('a * 'x, 'b * 'x, 'a, 'b) t`

`val snd : ('x * 'a, 'x * 'b, 'a, 'b) t`

`val some : ('a option, 'b option, 'a, 'b) t`

On This Page