dbuenzli / cmdliner

Declarative definition of command line interfaces for OCaml
http://erratique.ch/software/cmdliner
ISC License
296 stars 56 forks source link

Add applicative binders `let+` and `and+` to the API of `Term`s #173

Closed esope closed 5 months ago

esope commented 1 year ago

It would be very useful to expose the following two functions to the API of Terms.

let ( let+ ) v f =
  let open Term in
  const f $ v
(* val ( let+ ): 'a Term.t -> ('a -> 'b) -> 'b Term.t *)

let ( and+ ) v1 v2 =
  let open Term in
  const (fun x y -> (x, y)) $ v1 $ v2
(* val ( and+ ): 'a Term.t -> 'b Term.t -> ('a * 'b) Term.t

This is very handy to write, say, a term that composes several command line options into a record of values. For example, suppose you already have defined terms opt1_t: bool Term.t, opt2_t: int Term.t and opt3_t: string Term.t. You could then write:

type opt = { opt1: bool; opt2: int; opt3: string }

let opt_t : opt Term.t =
  let+ opt1 = opt1_t
  and+ opt2 = opt2_t
  and+ opt3 = opt3_t
  in
  { opt1; opt2; opt3 }

Of course, any (advanced) user of the library can write these combinators herself, but I think there is some value in providing them to any user.

What do you think about adding let+ and and+ to cmdliner? (if this has been proposed already, I missed it in the submitted issues)

dbuenzli commented 1 year ago

What do you think about adding let+ and and+ to cmdliner?

I'm not very fond of adding them because it entices people to mix logic and cli parsing.

The style I'd like to encourage is have functions and then expose them to the cli via the Cmdliner machinery.

dbuenzli commented 1 year ago

(But I have to add) This:

let opt_t : opt Term.t =
  let+ opt1 = opt1_t
  and+ opt2 = opt2_t
  and+ opt3 = opt3_t
  in
  { opt1; opt2; opt3 }

is clearly a very good argument to add them since it avoids having to write a record constructor.

gasche commented 10 months ago

Note: in recent OCaml versions binding operators support a weird form of punning, let+ x in ... for let+ x = x in ..., so one can even write

let+ opt1
and+ opt2
and+ opt3
in { opt1; opt2; opt3 }
dbuenzli commented 10 months ago

That rather looks like an argument against adding them…

smondet commented 5 months ago

I'm not very fond of adding them because it entices people to mix logic and cli parsing.

I have the impression that it does the opposite. Without the let+/and+ one has to keep (part of) the implementation close/tied to the CLI parsing because the order of the application of the $ needs to be the same as the one of the function (and once there are more than 4 or 5 options it becomes tedious to count, and error prone if too many of the arguments have compatible types).

let+ well_named_arg = Arg.( ... ) (* <- naming of value next to definition *)
and+ an_actual_option = Arg.( ... )
and+ actually_the_first_arg = Arg.( ... )
(* ... *)
in
call_to_the_function_without_worrying_about_the_order
   ~actually_the_first_arg ?an_actual_option ~well_named_arg ()
dbuenzli commented 5 months ago

What you point to is not strictly speaking about mixing logic and cli parsing.

But it is certainly annoying and a weakness of $ that you can't use labelled arguments. And you are right that it's easy to confuse yourself and plug the wrong bits in some place.

The latter for me is sufficient to justify their inclusion.

dbuenzli commented 5 months ago

It's in. Now go kill all these $.