The upcoming OCaml 4.08 release will allow developers to define custom bindings operators. We were eager to use this feature in the code of Dune but because we are currently keeping compatibility with all versions of OCaml since 4.02, we decided to implement a preprocessor shim for older OCaml versions. Given that this shim works quite well, we are also making it available for users of Dune starting with version 1.8 which will be released soon. This post explains how to use this new feature.
The future_syntax preprocessor
If you want to use custom bindings in your code but need to keep your code compatible with OCaml < 4.08, you can use the special
future_syntax preprocessor introduced in Dune 1.8. To do that, simply add the following field to your library/executable stanza:
When using OCaml >= 4.08, this is exactly equivalent to just deleting this field. This means that
future_syntax doesn’t add overheard when using a recent version of the compiler. When using using OCaml < 4.08, this will automatically add a pre-processor that will translate special
and+, … operators into valid pre-4.08 OCaml code, allowing your code to compile with an older compiler.
The shim preprocessor converts bindings operators to OCaml identifiers of the form
and__XXX. For instance,
let+* is translated to
let__plus_star. So you must make sure to not use such identifiers in your code.
The following example uses the
future_syntax preprocessor and bindings operators in code using the cmdliner library.
(executable (name foo) (libraries cmdliner) (preprocess future_syntax))
open Cmdliner let ( let+ ) t f = Term.(const f $ t) let ( and+ ) a b = Term.(const (fun x y -> x, y) $ a $ b) let term = let+ a = Arg.(value & flag & info ["a"] ~doc:"blah") and+ b = Arg.(value & flag & info ["b"] ~doc:"blah") and+ c = Arg.(value & flag & info ["c"] ~doc:"blah") in Printf.printf "a=%B b=%B c=%B\n" a b c let cmd = (term, Term.info "foo" ~version:"v1.0.3" ~doc:"example") let () = Term.(exit @@ eval cmd)
You can test this example with:
$ dune exec ./foo.exe -- -a -b
Without bindingins operators,
foo.ml would have to be written as follow:
open Cmdliner let term = let main a b c = Printf.printf "a=%B b=%B c=%B\n" a b c in Term.(const main $ Arg.(value & flag & info ["a"] ~doc:"blah") $ Arg.(value & flag & info ["b"] ~doc:"blah") $ Arg.(value & flag & info ["c"] ~doc:"blah") ) let cmd = (term, Term.info "foo" ~version:"v1.0.3" ~doc:"example") let () = Term.(exit @@ eval cmd)
Which shows that binding operators are especially nice when working with such API; indeed, without binding operators the authors and readers of the code have to manually match the order of arguments passed to
main with the order of the
Arg.(...) expressions inside the
Term.(...) expression. With binding operators, the OCaml variable to which the evaluation of the command line argument is bound is right next to its definition, which is much nicer to read and write.