leostera / caramel

:candy: a functional language for building type-safe, scalable, and maintainable applications
https://caramel.run
Apache License 2.0
1.05k stars 25 forks source link

Pipe operator support #72

Open michallepicki opened 3 years ago

michallepicki commented 3 years ago

It would be great to have support for the pipe operator. It is a common way in many functional programming languages to compose functions into a readable left-to-right or top-to-bottom "pipeline" of steps of computation.

Documentation in OCaml stdlib Documentation in Elixir Kernel

I think it should probably behave like in OCaml and not like Elixir, meaning this Caramel code in file main.ml when ran with ocaml compile main.ml && escript main.erl would print 2 (notice the order of arguments in subtract and divide):

let print_int number = Io.format "~0tp~n" [ number ]

let subtract x y = y - x
let main _ =
  let divide x y = y / x in
  10 |> subtract 2 |> divide 4 |> print_int

so it would be equivalent to this Caramel code:

let print_int number = Io.format "~0tp~n" [ number ]

let subtract x y = y - x
let main _ =
  let divide x y = y / x in
  print_int (divide 4 (subtract 2 10))

and behave the same as this toplevel OCaml code:

(* let print_int number = Io.format "~0tp~n" [ number ] *)

let subtract x y = y - x ;;
let main _ =
  let divide x y = y / x in
  10 |> subtract 2 |> divide 4 |> print_int ;;

main ()

when executed:

$ ocaml main.ml
2
michallepicki commented 3 years ago

The easiest way to make it work right now I think, would be to make the partial function application compile to anonymous functions first (tracked in https://github.com/AbstractMachinesLab/caramel/issues/44) and then introduce a pipe function in caramel_runtime that this operator could compile to, which would take an argument and a function, and it would apply the argument to that function. It still wouldn't work when running with the escript example from above, and it would be unreasonably slow, but I think at least it might work!

leostera commented 3 years ago

I agree that I'd expect this to behave like in OCaml. I also think that I should bring up that ReScript and Reason chose to favor "pipe first" (->) instead, which behaves just like Elixir's pipe, because it was said that it type inference with records was better.

I haven't really experienced any problems with |> that I can recall right now, to be honest, just felt that I should bring this up.

ayshiff commented 3 years ago

@ostera I would love to implement this!

leostera commented 3 years ago

@ayshiff dope! 🚀 the easiest would be to expose (|>) as an external that maps to a caramel_runtime:pipe/2 function somewhere.

But feel free to give it an initial stab by just hacking around the compiler, and we can then refine it 🙌🏽