README.md 4.7 KB

ppx_lens

ppx_lens is an OCaml PPX for generating "lens" operations on records, which helps to facilitate functional updates on deeply nested data structures.

Examples

Generate lens functions for a record:

# type glasses =
    { len : int;
      bridge : int;
      diam : int }
        [@@lens generate] ;;
type glasses = { len : int; bridge : int; diam : int; }
val len : glasses -> int = <fun>
val bridge : glasses -> int = <fun>
val diam : glasses -> int = <fun>
val set_len : int -> glasses -> glasses = <fun>
val set_bridge : int -> glasses -> glasses = <fun>
val set_diam : int -> glasses -> glasses = <fun>
val update_len : f:(int -> int) -> glasses -> glasses = <fun>
val update_bridge : f:(int -> int) -> glasses -> glasses = <fun>
val update_diam : f:(int -> int) -> glasses -> glasses = <fun>
val _len : (glasses -> int) * (int -> glasses -> glasses) = (<fun>, <fun>)
val _bridge : (glasses -> int) * (int -> glasses -> glasses) = (<fun>, <fun>)
val _diam : (glasses -> int) * (int -> glasses -> glasses) = (<fun>, <fun>)

# let g = set_diam 56 {len = 145; bridge = 17; diam = 54} ;;
val g : glasses = {len = 145; bridge = 17; diam = 56}

[@@lens generate] generates a few functions for each field of the record, specifically:

  • FIELDNAME: function for getting the value of a field
  • set_FIELDNAME: function for changing the value of a field
  • update_FIELDNAME: function for changing the value of a field with a function

Additionally it generates "lens" values (denoted with underscore prefix), which are just pairs consisting of the getter and setter functions. See lenslib below for ways to manipulate these values.

Installation

ppx_lens is best installed with OPAM and dune.

$ git clone <url>/ppx_lens.git
$ opam pin add ppx_lens ppx_lens/
$ opam pin add lenslib ppx_lens/    # optional

TODO: publish to OPAM servers

dune (jbuilder) configuration:

(library
 ((name ...)
  ....
  (preprocess
   (pps (... ppx_lens ...)))))

Configuration

The functions generated by ppx_lens can be configured by supplying named arguments.

Usage:

type t = { <record field> ... }
    [@@lens generate <parameter> ...]

Availabe parameters:

  • ~field_prefix:<prefix>: change what prefix to place before the field names
  • ~field_prefix_from_type: use the name of the type to determine the prefix
  • ~self_arg_first: place the argument for the structure being modified before the other arguments, in setter / updater functions. By default, the structure comes after the other argument.
  • ~get_prefix:<prefix>: change what prefix to use for getter functions. By default, getter functions do not have a prefix.
  • ~set_prefix:<prefix>: change what prefix to use for setter functions. By default, setter functions use the prefix set.
  • ~update_prefix:<prefix>: change what prefix to use for updater functions. By default, updater functions use the prefix update.
  • ~func_named_arg:<name>: change the named argument in the updater function. By default, the argument is named f.
  • ~func_no_named_arg: don't name the argument in the updater function. The position of the argument may then be controlled with ~self_arg_first.
  • ~no_get: don't generate getter functions.
  • ~no_set: don't generate setter functions.
  • ~no_update: don't generate updater functions.
  • ~no_lens: don't generate lens values.
  • ~just_lens: don't generate getter / setter / updater functions, just lens values.

TODO: example here

Lenslib

This repository contains a small library called lenslib with functions for manipulating the "lens" values generated by ppx_lens. Note that lenslib is not a dependency for ppx_lens.

module Lenslib :
  sig
    type ('s, 't, 'a, 'b) lens = ('s -> 'a) * ('b -> 's -> 't)
    type ('s, 'a) lens' = ('s, 's, 'a, 'a) lens
    val _1 : ('x * 'a, 'y * 'a, 'x, 'y) lens
    val _2 : ('a * 'x, 'a * 'y, 'x, 'y) lens
    module Lens :
      sig
        type ('s, 't, 'a, 'b) t = ('s, 't, 'a, 'b) lens
        type ('s, 'a) t' = ('s, 'a) lens'

        val view : ('s, 't, 'a, 'b) t -> 's -> 'a
        val set : ('s, 't, 'a, 'b) t -> 's -> 'b -> 't
        val over : ('s, 't, 'a, 'b) t -> f:('a -> 'b) -> 's -> 't
        val compose : ('a, 'b, 'c, 'd) t -> ('c, 'd, 'e, 'f) t -> ('a, 'b, 'e, 'f) t
        val ( ^. ) : 'a -> ('a, 'b, 'c, 'd) t -> 'c
        val ( ^> ) : ('a, 'b, 'c, 'd) t -> ('c, 'd, 'e, 'f) t -> ('a, 'b, 'e, 'f) t
      end
  end

Examples

# open Lenslib
# open Lens
# g ^. _bridge ;;
- : int = 17
# set (_2 ^> _len) (true, g) 160 ;;
- : bool * glasses = (true, {len = 160; bridge = 17; diam = 56})

TODO: create ocamldoc