A ppx rewriter that provides pattern matching on abstract types by transforming patterns into views/expressions.
ppx_view
transforms the patterns of match
/function
constructs
wrapped inside [%view]
extensions by replacing matches against
constructors with function calls.
For instance, in the following code:
match%view expr with
| Constr var -> var
the constructor Constr
is turned into a call to the function
constr
, which is expected to be a view pattern:
val constr : (string, 'a, 'b) View.t -> (expression, 'a, 'b) View.t
Technically, the above expression is rewritten into:
Viewlib.View.match_ __POS__
[Viewlib.View.case (constr Viewlib.View.__)
(fun (Viewlib.View.Var_snoc (Viewlib.View.Var_nil, var)) -> var)]
expr
where __
is used to capture the variable.
ppx_view
applies the following mapping:
-
a literal constant
c
of typetyp
is mapped toView.typ c
; -
an interval pattern
c1..c2
is mapped toView.interval e1 e2
whereci
is mapped toei
; -
a variable pattern is mapped to
View.__
; -
a catch-all pattern is mapped to
View.drop
; -
a record pattern
{ lbl1 = p1; lbl2 = p2; ... }
is mapped tolbl1'match e1 (lbl2'match e2 ...)
wherepi
is mapped toei
; -
a constructor pattern
C (p1, ..., pn)
is mapped toc e1 ... en
wherepi
is mapped toei
, except for constructors from the core library:Some
is mapped toView.some
;None
is mapped toView.none
;::
is mapped toView.cons
;[]
is mapped toView.nil
;()
is mapped toView.unit
;true
is mapped toView.true_
;false
is mapped toView.false_
.
Note: the following patterns are currently not supported:
- polymorphic variants;
- lazy;
- module unpacking;
- exceptions.
The Parseview
module of the library contains the functions
corresponding to the constructors and records from the Parsetree
module. Such functions use "shortcuts" to xyz_desc
fields, allowing
to directly match constructors:
open Viewast
let is_zero : Parsetree.expression -> true = function%view
| Pexp_constant (Pconst_integer ("0", _)) -> true
| Pexp_ident { txt = Lident "zero"; _ } -> true
| _ -> false
The access to other fields is done through a [@view ...]
annotation:
open Viewast
let is_zero : Parsetree.expression -> true = function%view
| (Pexp_constant (Pconst_integer ("0", _)))[@view { pexp_loc; }] -> true, pexp_loc
| Pexp_ident { txt = Lident "zero"; loc; } -> true, loc
| _ -> false, Location.none
The library also provides an Ast_viewer
module that acts as the counterpart
of the Ast_helper
module. It allows to write patterns very similar to the
corresponding expressions:
open Viewast
let twice_mapper =
let module H = Ast_helper in
let module M = Ast_mapper in
let module V = Ast_viewer in
let super = M.default_mapper in
let expr self e =
match%view super.expr self e with
| V.Exp.Constant (V.Const.String (str, _)) ->
H.Exp.constant (H.Const.string (str ^ str))
| other ->
other
and pat self p =
match%view super.pat self p with
| V.Pat.Constant (V.Const.String (str, _)) ->
H.Pat.constant (H.Const.string (str ^ str))
| other ->
other
in
{ super with expr; pat; }
Following the proposal in MPR#7628,
not pattern are implemented through the Not
constructor, that is
rewritten to View.not
. Not patterns allow to write patterns that
are matched if a subpattern is not matched as in:
match%view expr with
| Pexp_constant (Not (Pconst_integer _ | Pconst_float _)) ->
(* expr is not a number *)
| Pexp_constant (Pconst_integer _) ->
(* expr is an integer *)
| Pexp_constant (Pconst_float _) ->
(* expr is a float *)
Not patterns cannot contain variables.
The major limitations of view patterns are the lack of checks for non-redundancy and exhaustivity.