Skip to content
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

Making pipe operators primitive #3122

Merged
merged 7 commits into from
Dec 9, 2023
Merged

Conversation

mtzguido
Copy link
Member

@mtzguido mtzguido commented Dec 1, 2023

This PR makes the operators |> and <| into primitives recognized by the parser and desugarer, which are interpreted as applications, just with different associativity and precedence.

This is good (IMO) since:
1- We get "effect polymorphism" for free: these are just applications, while if we define the operators must choose an effect
2- Base files in the compiler get a bit of cleanup, since they had these definitions
3- The generated OCaml looks simpler
4- SMT queries may be simpler
5- One does not inadvertedly lose equations if using an effectful <| or |>, when both the function and arguments are total. This actually led to a regression in everparse, where the snippet

let print_binding mname (td:type_decl)
  : ML (string & string)
  = let tdn = td.name in
    let typ = td.typ in
    let k = td.kind in
    let root_name = print_ident mname tdn.td_name in
    let print_binders binders =
        List.map (print_param mname) binders |>
        String.concat " "
    in
    let print_args binders =
        List.map (fun (i, _) -> print_ident mname i) binders |>
        String.concat " "
    in
    ...

was now failing, I suppose since print_binders and print_args now have SMT definitions and that was too much (but I didn't dig deep into it..). Annotating them as ML string fixes the issue here. I'd argue this is generally a good thing, though..?

Disadvantages:
1- The operators cannot be redefined (hence the patch in DoublyLinkedList)
2- The use of the operators is currently lost after desugaring, so it won't be printed by --dump_module (but --print works). This could be fixed by keeping some meta_desugared info.

(paragraph edited, got confused)
Note that F# has these operators too, where both of them associate to left. So |> behaves the same, but one can write f <| X <| Y <| Z to get f (X) (Y) (Z). F* also had this behavior, so we are changing that by changing the associativity of <|, but we think the symmetry is nicer. A pipeline of passing x though functions f, g and h in that order can then be written x |> f |> g |> h or h <| g <| f <| x.

In the parser, I made <| have more precedence that |>, see tests here. This is totally arbitrary, not sure if it's the right thing.

OCaml did something similar, also giving |> and @@ (our <|) special handling as an application: see ocaml/ocaml#10081.

As a follow up I want to change the precedence rules to allow snippets like (slightly edited):

let generalize env is_rec lecs = 
  Errors.with_ctx "While generalizing" (fun () ->
    Profiling.profile (fun () -> generalize' env is_rec lecs)
  )

be written in this form:

let generalize env is_rec lecs = 
  Errors.with_ctx "While generalizing" <| fun () ->
  Profiling.profile <| fun () ->
  generalize' env is_rec lecs

which is a common (I think) style in OCaml. This requires some deeper parser surgery, so I'm not bundling it here.

mtzguido added a commit to mtzguido/everparse that referenced this pull request Dec 1, 2023
@nikswamy nikswamy merged commit 89a10e3 into FStarLang:master Dec 9, 2023
2 checks passed
@mtzguido mtzguido deleted the pipe_operators_1 branch January 2, 2024 18:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants