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

Feature: Using dot operator instead of pipe operator #1638

Closed
kasperpeulen opened this issue Nov 13, 2017 · 11 comments
Closed

Feature: Using dot operator instead of pipe operator #1638

kasperpeulen opened this issue Nov 13, 2017 · 11 comments

Comments

@kasperpeulen
Copy link

kasperpeulen commented Nov 13, 2017

Consider the following example:

let (map, fold_left, filter) = List.(map, fold_left, filter);
let (log) = Js.(log);

let list = [1,2,3,4,5,6,7,8,9,10];
let manipulated = list 
  |> filter(i => i < 5) 
  |> map(i => i + 1) 
  |> fold_left((p,c) => p + c, 0);

manipulated |> log;

Coming from Javascript, this may look very foreign. However, if we would introduce the dot (.) operator, to mean the same as the pipe operator, then this would look like this.

let list = [1,2,3,4,5,6,7,8,9,10];
let manipulated = list
  .filter(i => i < 5)
  .map(i => i + 1)
  .fold_left((p,c) => p + c, 0);

manipulated.log;

This is almost exactly how it looks in javascript. Another adventage is that you only have one character to type.

I'm just learning Reason, so I probably don't really oversee what the consequences would be for introducing this syntax.

One challenge that I see. Say you have a record: let record = {foo: "foo", bar: "bar"} and there is also a function foo available in the same scope, where the type of record is the last parameter.
What happens, if you do record.foo? I guess it makes most sense (and non-breaking) if the properties of the record have higher priority. If you would want to use the function foo act on record, you should cast the function foo to a different name.

One other thing that feels strange to me (as Javascript programmer), is that the pipe operator takes the last argument. I understand that this most natural in the context of auto currying, and defining x |> f, to be f(x) but if I would make my own API of functions acting on some type myType, I would most naturally put, put myType as first parameter.

@kasperpeulen kasperpeulen changed the title Feature: Using dot operator instead of pipe operator on records Feature: Using dot operator instead of pipe operator Nov 13, 2017
@OlegLustenko
Copy link

https://github.com/tc39/proposal-pipeline-operator

It seems different things

@kasperpeulen
Copy link
Author

kasperpeulen commented Nov 13, 2017

@OlegLustenko Yes, they are different things.

But as as the dot has no meaning in the context of Tuples, List and only the meaning of accessing properties in records, I thought, you could maybe extend the meaning of the dot operator.

In some sense, the pipeline operator and methods in a OOP language, are similar. Consider the following javascript class.

class Point {
  constructor(x, y) {
    this.x;
    this.y;
  }

  add(point) {
    return new Point(this.x + x, this.y + y);
  }

  distance() {
    return this.x**2 + this.y**2;
  }
}

I don't need to put the methods add and distance on the Point class. I could define them as pure functions. But I do this in javascript, because I prefer chaining things like this and because of autocompletion:

point1.add(point2).distance()

In reason, I can write the code like this:

type point = {x:int, y:int};

let add = (p1: point, p2: point) => {
  let x = p1.x + p2.x;
  let y = p1.y + p2.y;
  {x,y};
};

let distance = (point: point) => {
  point.x*point.x + point.y*point.y;
};

Similarly, as in javascript, I can chain things, but even more powerfully, with the pipe operator:

point1 |> add(point2) |> distance;

That is why I think the pipe operator, and methods access in OOP are similar. In my proposal, you would write it like this:

point1.add(point2).distance

@cullophid
Copy link

I'm not sure this would make it easier for js devs. The pipe operator is very different from method chaining.

@kasperpeulen
Copy link
Author

It seems like D, Rust and Nim have something similar:
https://en.wikipedia.org/wiki/Uniform_Function_Call_Syntax

I'm not sure this would make it easier for js devs. The pipe operator is very different from method chaining.

The difference between methods and functions is that the this argument is explicit. In the example above, semantically there is not much difference between point1.add(point2)in javascript and add(point1, poin2) in Reason.

Syntactically, there is a big difference, the main downside of the function syntax is that functions can not be easily chained. The pipeline operator solve this syntax issue. My proposal is to go even further, and make no syntactical difference at all. As D, Rust and Nim also seem to do.

@jaredly
Copy link
Contributor

jaredly commented Nov 14, 2017

@kasperpeulen are you proposing that we change the record access syntax? What would the new syntax be for "I want the bar attribute of the foo record"? (currently foo.bar). (If you wanted them both to work, it would require significant compiler work, unfortunately, not just a syntax change).

The "pipe dot" proposal, on the other hand, could be implemented without any other fallout.

One reason Rust is able to do what it does is that its inference engine is much more restricted -- you have to annotate each function signature (as opposed to Reason, where very little must be annotated).

@kasperpeulen
Copy link
Author

@jaredly Yes I would say that then foo.bar means look up the attribute bar on foo, if the attribute bar doesnt exist look up the function bar and parse it like foo |> bar

This may indeed give significant compiler work.

I guess you are right about it being a better match for Rust. I think you would need function overloading for this to make real sense. And annotating your function parameters.

Also, from a javascript background, Im used to importing functions. Talking to people on discord, it seems like in Reason people prefer writing:
list |> List.map(...) |> List.filter(...) |> etc.

In that way, my proposal would become messy and less reasable:
list.List.map(...).List.filter(...)

So Im not so sure myself anymore if this proposal would make sense for Reason. But I let you guys to decide.

@jaredly
Copy link
Contributor

jaredly commented Nov 15, 2017

Thanks for contributing to the discussion, regardless!

@jordwalke
Copy link
Member

jordwalke commented Nov 16, 2017

I do think of |> almost as OO's .. For example, imagine a . with spaces around it that is equivalent to the |> operator. Then, the following:

myList |> List.map(fn)

Could be written as:

myList . List.map(fn)

And the parallels between FP and OO become very clear.

In fact, a dot with spaces around it isn't even ambiguous with record field access so we could in theory add such a syntax for |>.

As you pointed out the one thing that would really make this analogy to OO patterns would be the existence of Haskell style "type classes", but even without that - it's still a cool idea.

Unfortunately, we cannot remove the spaces around the ., because the following already means something entirely different today:

myList.List.map(fn)

It means "myList definitely is a record, and it has a field named .map, and let me tell you which module that record field is defined in - it's in List".

@jaredly
Copy link
Contributor

jaredly commented Nov 16, 2017

I think I might vote for . to be the syntax transform proposed in #1452 (a . b(c) becomes b(a, c), a syntax transform, not a runtime operator) -- clojure's ->, where |> is clojure's ->>.

@jordwalke
Copy link
Member

Yeah, I was thinking the same thing. 😄

@jaredly
Copy link
Contributor

jaredly commented Jun 14, 2018

We now have |., and we're potentially going to have -> as well :) let's continue the discussion over here: #1999

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

No branches or pull requests

5 participants