New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Syntax proposal: let punning #10013
Syntax proposal: let punning #10013
Conversation
Indeed: Olivier Martinot and myself would have a use of "let punning" when manipulating applicative functors with let operators, we use an "unpack" idiom The reason why this pattern is so common is that it emulates "box" rules for modalities. The modal typing rule:
is expressed in OCaml as
|
An alternative approach that could be applied for binding applicative/monadic parameters would be extending the pattern syntax to enable monadic unpacking of the function parameters: let add +x +y =
x + y which unfolds to let add x y =
let+ x = x and+ y = y in
x + y This will move back abstraction/application on par with let binding, as the latter became superior to the former after the introduction of the binding operators. Since the change is on the pattern level it will also affect the let-bindings itself, so that I tend to believe that this approach addresses the essence of the problem of tautological bindings and fits more or less naturally into the language semantics, cf. the lazy pattern, e.g., Finally, if we will allow spaces between the monadic operator and the pattern itself, e.g., |
Another use case, similar to the third one, for bringing an explicit set of values from another module into scope: open struct open M let x and y and z end |
I'm not sure
|
I think the But (a) this is a much larger and trickier feature than let-punning, and (b) even if we had this feature, I'd still want let-punning to avoid having to write |
Indeed, introducing it on the pattern level is probably too ambitious. My original idea, which I prematurely generalized, was to introduce it on the parameter level or even higher, on the
This will keep patterns pure and will minimize surprise as we already have something equal with the |
I don't think that rewriting would do what you expect, because of OCaml's pervasive currying. The two-argument function let f +x +y = ... into let f x = let* x = x in fun y -> let* y = y in ... where the first |
In my current project, we have a ppx: [%%define_locally M.(x,y,z)] which expands to let x,y,z = M.(x,y,z) With the proposed syntax, could you write: open M
let x,y,z to achieve the same re-exporting result, instead of having to separate the bindings by |
No, include struct
open M let x and y and z
end would be the shortest expansion. We could possibly allow include M.(struct let x and y and z end) |
Allowing (I do like @gasche's |
I see the benefit for I'm also curious about the user experience if someone accidentally writes |
If someone writes If there happens to be an |
abcffd4
to
d65593b
Compare
The consensus at a recent developer meeting was that the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The implementation looks fine to me -- and I also like the proposed syntax.
Changes
Outdated
@@ -3,6 +3,9 @@ Working version | |||
|
|||
### Language features: | |||
|
|||
- #??: Let-punning | |||
(Stephen Dolan, review by ??) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess we need to refine this to "binding-operator punning". Could you include an actual example (and its desugaring)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, are you sure that it is let-punning not let-prunning?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The name we use really is "punning". I think of it as a "pun" in that we use something for something else (the name of a declaration as its definition). The naming "record field punning" is well-established and more or less (well, remotely) consistent with type punning for example.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
haha, I was always under impression that I was using type pruning)) Good day I have learned something new)
It was pointed out to me that lots of binding-operator-style code that could benefit from this feature doesn't use the So, I've just pushed a version of this patch that re-adds the support for let-punning on plain |
@@ -458,6 +458,7 @@ let extra_rhs_core_type ct ~pos = | |||
type let_binding = | |||
{ lb_pattern: pattern; | |||
lb_expression: expression; | |||
lb_is_pun: bool; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I we are going to have this wart in the codebase, let's turn it into a strength that will make ocamlformat people happy: use it also for binding operators, and use this boolean when reprinting the AST.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not quite sure what you're suggesting here? The type let_binding
is not part of the AST, but rather a temporary structure used by parser.mly
to share code between various constructs that have similar syntax.
parsing/parser.mly
Outdated
@@ -2433,7 +2440,7 @@ let_binding_body: | |||
let typ = ghtyp ~loc (Ptyp_poly([],t)) in | |||
let patloc = ($startpos($1), $endpos($2)) in | |||
(ghpat ~loc:patloc (Ppat_constraint(v, typ)), | |||
mkexp_constraint ~loc:$sloc $4 $2) } | |||
mkexp_constraint ~loc:$sloc $4 $2, false) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should avoid these false
blemishes in our semantic actions with the following setup:
let_binding_body:
| let_binding_body_with_punning { let (p,e) = $1 in (p, e, true) }
| let_binding_body_no_pun { let (p, e) = $1 in (p, e, false) }
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is better, I'll change to something along those lines. (I don't think let_binding_body_with_punning
is necessary as a separate nonterminal, though, as there's only one case)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code looks okay and I understand what it does and why it is this way. Approved.
| let_binding_body_no_punning | ||
{ let p,e = $1 in (p,e,false) } | ||
| val_ident %prec below_HASH | ||
{ (mkpatvar ~loc:$loc $1, mkexpvar ~loc:$loc $1, true) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: I suspect that if you had used a punned_binding
rule for this case, you could have shared it with the letop_binding_body
grammar. (The %prec
annotation would stay here, I guess?)
pp f "@[<2>%s %s@]" x.pbop_op.txt evar | ||
| pat, exp -> | ||
pp f "@[<2>%s %a@;=@;%a@]" | ||
x.pbop_op.txt (pattern ctxt) pat (expression ctxt) exp |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remark: if I understand correctly, we have pretty-printing support for the binding operator form, but not for the extension form. This is just fine as far as I am concerned.
If I'm not mistaken, let punning has not been added to the OCaml manual. This needs to be done before the feature is released. @stedolan, could you take care of this? (I think it would naturally be a new section in the "Language extensions" chapter; I considered extending the "Binding operators", but it doesn't really make sense if it also applies to extension-lets.) |
Done in #10202. I added it to "Binding operators" and cross-referenced from extension-lets. |
Let-punning: allow "let* x" and "let%foo x" syntax without an explicit binding.
OCaml supports "punning": when a record field / named argument / object method has the same name as a variable, it need not be written twice. For instance, instead of:
one can write the more concise:
This patch extends punning to work with let bindings, allowing
let x
as shorthand forlet x = x
.This is much more useful that it might first appear.
Anonymous functor parameters
When using
Map
,Set
and similar container types,let compare = compare
is a common pattern, which let-punning shortens:Binding operators
When writing monadic or applicative code using binding operators, the pattern
let* foo = foo
is common, representing a monadic bind / applicative map of a variable. (Without binding operators, this is usually written asfoo >>= (fun foo -> ...)
orfoo |> map (fun foo -> ...)
). Let punning means that this example from the OCaml manual:can become:
(This application was the original motiviation for this feature)
Re-exporting values
Modules which re-export most of their contents from another module can be more concisely written with let-punning. For instance:
can become: