Introduce Expression Pipe Operator #6455

Closed
dlreeves opened this Issue Oct 30, 2015 · 11 comments

Projects

None yet

10 participants

@dlreeves
Contributor

This is a part of the Hack array proposal

To put simply, writing in a functional style with arrays can produce some ugly code. Consider this example

// arrays
count(
  array_filter(
    array_map(
      $x ==> $x->getNumber(),
      array(...),
    ),
    $x ==> is_even($x),
  )
)

Since Collections are objects, methods calls can be chained to produce a more fluid interface. Here is the same piece of code written in terms of collections.

// collections
(Map {...})
  ->map($x ==> $x->getNumber())
  ->filter($x ==> is_even($x))
  ->count()

This code is considerably easier to read because the operations are chained one after another. First you map, then you filter, then you count. Functions do not have this property, so any functional API involving arrays will lead to the deep nesting demonstrated earlier. This can be fixed by introducing some sugar syntax borrowed from functional languages such as F# and OCaml.

// map -> filter -> count
array(...)
  |> array_map($x ==> $x->getNumber(), $$)
  |> array_filter($$, $x ==> is_even($x))
  |> count($$)

The |> operator will be known as the expression pipe operator. It takes the value on the left of the operator, evaluates it and passes the result into the placeholder on the right of the operator. This is analogous to piping in a Linux command line. The $$ is the placeholder for where the result of the left expression will be substituted. The beauty of this solution is that it can be transformed trivially to the nested function style by the HHVM compiler, meaning this will have zero runtime impact.

This can also improve the utility of Collections or other APIs that rely heavily on method chaining. Now static utility methods can be added without breaking the style.

function compact<T>(Traversable<?T> $vector): Vector<T> {
...
}

// What is happening here?
compact($vec->map($x => $x?->foo()))->count(); 

// Maintain chaining style
$vec
  ->map($x ==> $x?->foo())
  |>compact($$)
  ->count()
@steelbrain
Member

IMO the syntax of this is too confusing. It would be nice if you would provide us with an example about how this code is going to be transpiled like

// Source:
$arr = get_some_array()
  |> do_something($$)
// Output
$arr = do_something(get_some_array());

Other questions I have in mind are

  • Does this operator work on the same line? or is a new line a requirement?
  • Can we use this for non-array things?
@Orvid
Contributor
Orvid commented Oct 31, 2015

The syntax of this is, while not quite as clean as D's Uniform Function Call Syntax (UFCS), is far cleaner than the alternative of not having anything. Due to some limitations of UFCS, in particular that the argument that's being chained on ($$ in this proposal) must be the first parameter, I think this is a better way of doing it.

@SiebelsTim
Contributor

Either I misunderstood something, or you have a bug in your last example. I assume you didn't want to pass an argument to the count method.

@dlreeves
Contributor

@SiebelsTim - Thanks. Had it confused with the count function

@StefanKarpinski StefanKarpinski referenced this issue in JuliaLang/julia Oct 31, 2015
Open

Function chaining #5571

@Daniel15
Member
Daniel15 commented Nov 2, 2015

Why not just use collections?

@simonwelsh
Contributor

@Daniel15 that's covered in the linked blog post

@Daniel15
Member
Daniel15 commented Nov 2, 2015

Thanks, somehow I totally missed that link.

@mglinski
mglinski commented Nov 3, 2015

Damn, having a expression-pipe operator would be soooooo nice in vanilla php. Once you get this implemented, you should put in a RFC to add it for 7.x.

@jwatzman jwatzman added the hack label Nov 8, 2015
@paulbiss
Contributor

This is done on the hhvm side (992c816), I think we're still missing hack support. cc @dlreeves

@dlreeves
Contributor

Yep we are currently working on adding the type checker support. Initially we won't have support for this in h2tp (the dehackificator) because the transformation we do in the runtime is a bit more complicated than I presented in the original issue. Namely we cannot simply de-nest the operator into nested function calls because of potential side effects. Instead we create temporary variables to store the result of the left operand.

If there is demand for support of the future for h2tp I think the simplest way to do the transpile is nested closures, i.e.

1 |> $$ + $$

Could be translated to

(function($x) {
  return $x + $x
})(1)
@dlreeves
Contributor

The type checker support for this feature has now landed. Please play around with it and report any issues you run into

@SiebelsTim SiebelsTim referenced this issue in hhvm/user-documentation Mar 1, 2016
Merged

Pipe operator #285

@aorenste aorenste closed this Sep 27, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment