Function.pipe_to/2

325 views
Skip to first unread message

José Valim

unread,
Aug 9, 2019, 10:28:25 AM8/9/19
to elixir-l...@googlegroups.com
Hi everyone,

With the addition of Function.identity/1, I would like to propose another function to the Function module: pipe_to/2.

The idea is that instead of:

"foo
|> String.upcase()
|> (&Regex.scan(~r/foo/, &1)).()

One can do:

"foo
|> String.upcase()
|> Function.pipe_to(&Regex.scan(~r/foo/, &1))

Or if you import it before:

"foo
|> String.upcase()
|> pipe_to(&Regex.scan(~r/foo/, &1))

While I wouldn't write the "pipe to anonymous" code, I have seen enough code in the wild that uses it that having a more readable (albeit more verbose) approach in the language sounds reasonable to me. The implementation can be inlined by the compiler to avoid the extra dispatch cost.

What are your thoughts? If you "pipe to anonymous functions" in your code today, would you prefer to use the new function? Yes/no? Why?

Thank you,

José Valim
Skype: jv.ptec
Founder and Director of R&D

Bruce Tate

unread,
Aug 9, 2019, 10:39:11 AM8/9/19
to elixir-l...@googlegroups.com
It's interesting to me. You have two things happening there: 

- partial application
- the  anonymous function invocation syntax. 

Which one are you solving here?

-bt

-bt

--
You received this message because you are subscribed to the Google Groups "elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-co...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/CAGnRm4L-4zcvLOnOFSFdnmk0foBJcu94QjrAv9-_QDySN%2BN9bg%40mail.gmail.com.


--

Regards,
Bruce Tate
CEO

José Valim

unread,
Aug 9, 2019, 11:20:15 AM8/9/19
to elixir-l...@googlegroups.com
The partial application was just the example of choice. It can be any anonymous function as argument.

--

Miguel Palhas

unread,
Aug 9, 2019, 1:43:13 PM8/9/19
to elixir-l...@googlegroups.com
The idea sounds interesting. You just got me thinking "do I avoid piping to anonymous functions only because I dislike the current syntax?")
Looking through my current project, I notice that more often than not, when I find myself needing something similar, I take that as a hint and extract the logic to a separate function:

value
|> do_stuff

defp do_stuff(value)
  # ...
end 

I like this approach better, as it makes the whole code even more explicit (I'm forced to come up with a meaningful name for that function). So I don't see myself using that function either.

Another concern I'm thinking of is the name. Mentally, how do you read "|> pipe_to(...)"?
Because I'm reading it as "pipe to pipe_to", which can turn out to be confusing



--
Best,
Miguel Palhas

Bruce Tate

unread,
Aug 9, 2019, 5:55:01 PM8/9/19
to elixir-l...@googlegroups.com
The more I look at this, the less I like it. If you really want to name the concept, why not go the extra mile and go to a named function? 

Just my opinion. 

-bt

Khaja Minhajuddin

unread,
Aug 9, 2019, 9:34:50 PM8/9/19
to elixir-lang-core

I like the idea, I think it would benefit the larger community as there might be valid cases where you want to use an anonymous function (if it is a really small one). However, the name seems confusing like Miguel pointed. I would name it `Function.capture` as it is kind of capturing the anonymous function and setting it up to be piped. Writing out the implementation for this makes this name more obvious
```
defmodule Function do
  def capture(arg, function) do
    apply(function, [arg])
  end
end


"foo"
|> Function.capture(fn x -> String.upcase(x) end)
|> IO.puts
```

Rich Morin

unread,
Aug 10, 2019, 12:55:32 AM8/10/19
to elixir-l...@googlegroups.com
Clojure has explicit syntax which handles the use case where one
doesn't want to pipe into the first argument of a function. It
only handles the first and last argument cases, but it could be
generalized pretty easily, as follows:

"foo"
|> String.upcase()
|2> Regex.scan(~r/foo/)

-r


Khaja Minhajuddin

unread,
Aug 10, 2019, 1:11:43 AM8/10/19
to elixir-lang-core
Another potential name for this could be `Function.apply` which actually seems more accurate as `capture` has another meaning in Elixir.

Devon Estes

unread,
Aug 10, 2019, 3:40:01 AM8/10/19
to elixir-lang-core
People have asked for some version of this a million times so they can use the pipe operator where the value being piped in has to be somewhere other than the first argument to the function, so clearly there’s a desire for such a thing.

Personally, though, I think adding this is relatively low value, and it will re-ignite a lot of bike shedding around the pipe operator and other ways people want that to function.

I mean, it’s fine, but I don’t think the language or the community is markedly better for having it.

Justin Wood

unread,
Aug 10, 2019, 7:45:32 AM8/10/19
to elixir-lang-core
> Another potential name for this could be `Function.apply` which actually
> seems more accurate as `capture` has another meaning in Elixir.

There is also Kernel.apply/2,3 which I think would cause more confusion.

OvermindDL1

unread,
Aug 12, 2019, 12:32:18 PM8/12/19
to elixir-lang-core
 I still think something like:

```
"foo"
|> String.upcase()
|> Regex.scan(~r/foo/, _)
```
Would be best, and would also replace the use of pipe_to (although still useful to have at times perhaps?), is backwards compatible, and is explicit because of the 'hole' there from the `_`.

Rich Morin

unread,
Aug 12, 2019, 2:23:45 PM8/12/19
to elixir-lang-core
> On Aug 12, 2019, at 09:32, OvermindDL1 <overm...@gmail.com> wrote:
>
> |> Regex.scan(~r/foo/, _)

I like this syntax better than my own suggestion:

|2> Regex.scan(~r/foo/)

because it doesn't mess with the `|>` syntax or require the reader to
count positions in the argument list.

However, I'm a bit concerned about adding new semantics to `_`. Its
current meaning tends to be "some arbitrary value", but the proposal
would add an alternate meaning of "the current pipeline value". Is
this likely to be a problem?

-r


Wiebe-Marten Wijnja

unread,
Aug 13, 2019, 4:26:12 AM8/13/19
to elixir-l...@googlegroups.com
I have a slightly different proposal: Why not allow `|> &Regex.scan(~r/foo/ &1)` directly?


The mentioned names for the 'wrapper macro that abstracts away the anonymous function application syntax' have all been names for things that already exist:

- `Function.pipe_to` is confusing because using `|>` already pipes something to something else.
- `Function.apply` is confusing because `Kernel.apply` already exists and its functionality sort of overlaps with it.
- `Function.capture` is confusing because the `&(&1)` syntax is known as 'capture operator' or 'shorthand capture syntax'.

I agree that there are many instances where it makes sense to encapsulate work in a separatly named function rather than piping through an anonymous function.
However, in examples such as the mentioned one, where it is only the order of the parameters that is sub-optimal (and when we remain at the same level of abstraction(!)), I find

this:

def find_foos(string) do
 
string
 
|> String.upcase()
 
|> &Regex.scan(~r/foo/, &1)
end


preferable over this:

def find_foos(string) do
 
string
 
|> String.upcase()
 
|> do_find_foos()
end

def do_find_foos(str)
  Regex.scan(~r/foo/, str)
end


---

I believe implementing this would be rather straightforward, since we only have to look at occurrences of the `&` operator inside the (top-level) macro expansion of `|>` (i.e. all magic occurs and disappears at macro expansion time).

~Marten/Qqwy
signature.asc

José Valim

unread,
Aug 13, 2019, 5:04:03 AM8/13/19
to elixir-l...@googlegroups.com
I really want to avoid new syntactical constructs or special casing anonymous functions as I believe it will be more confusing in the long term.

Imagine we can write this:

def find_foos(string) do
  string
  |> String.upcase()
  |> &Regex.scan(~r/foo/, &1)
end

Then if I have experience with other functional programming languages, I would expect to be able to write this:

def find_foos(string) do
  fun = &Regex.scan(~r/foo/, &1)
  string
  |> String.upcase()
  |> fun
end

But that won't work, it has to be "fun.()". So I think we need to work consistently with calls. Allowing something that is not a call will be confusing.

The other thing is that other proposals raised here (|2>, using _, etc) have already been discussed in the past and they have all been rejected. My position regarding those proposals have not changed. My intent was to bring a *new proposal* but it seems we won't have an agreement either. In any case, we should probably focus this thread on what is being currently proposed instead of bringing previous proposals back to life. :)

Regarding the name, one alternative I could come up with was "Function.into/2":

  string
  |> String.upcase()
  |> Function.into(&Regex.scan(~r/foo/, &1))

But it may read like the first argument is an anonymous function (especially if compared to Enumerable.into/2). The other option is Function.thread/2.

José Valim
Skype: jv.ptec
Founder and Director of R&D
--
You received this message because you are subscribed to the Google Groups "elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-co...@googlegroups.com.

OvermindDL1

unread,
Aug 13, 2019, 11:42:17 AM8/13/19
to elixir-lang-core
On Tuesday, August 13, 2019 at 3:04:03 AM UTC-6, José Valim wrote:
I really want to avoid new syntactical constructs or special casing anonymous functions as I believe it will be more confusing in the long term.

At that point adding in `.()` isn't a big deal, that's why it's pretty well fine as it is honestly for those kind of things.

The main issue is just piping into another 'argument' of a function call, like the erlang standard library very much expects the 'subject' to be at the end, so I end up having to do a lot of things like:
```
args
|> transform_them()
|> (&send(pid, &1)).()
```
When it would look and feel so much more clean via anything like:
```
args
|> transform_them()
|> &send(pid, &1) # Special case when `&` is in first position, though that does run into the expecting passing a `fun` to work

args
|> transform_them()
|> send(pid, _) # Pipe into the 'hole'

args
|> transform_them()
|> send(pid, _1) # Maybe index it, though this seems entirely needless

args
|> transform_them()
|> send(pid, &0) # Or use `&0` as a pipe value, though then it looks less like an obvious hole and thus more easy to 'scan' past

args
|> transform_them()
~> send(pid, _) # Perhaps a different pipe operator for piping into, but this seems needless when `|>` can do it just fine
```

All of which are much more easily read.  And there are libraries for doing just this, they 'feel' so much more clean when I use them.  With all of all of the above no closures even need to be created, it doesn't look like an anonymous function (except maybe the `&0` one), the prior value is just dumped into 'that' slot instead of the front slot.  Honestly I'd prefer if it always required `_`, no magic 'stuff into front position' or anything, this would even allow you to reference the value more than once (set it to a gensym'd name then and put that in the holes).

José Valim

unread,
Aug 13, 2019, 12:12:13 PM8/13/19
to elixir-l...@googlegroups.com
I have just asked to keep this thread on topic. In particular:

1. Can we focus on the pros and cons of what was proposed?

2. If we are going to propose other alternatives, can they be implemented using regular functions/macros, and without introducing or re-purposing existing syntactical constructs?

As said, most syntactical options have already been discussed extensively in the past. I am not personally interested in rehashing those arguments. If someone is interested, please do so in another thread.

Thank you,

José Valim
Skype: jv.ptec
Founder and Director of R&D

--
You received this message because you are subscribed to the Google Groups "elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-co...@googlegroups.com.

Eric Entin

unread,
Aug 13, 2019, 12:20:13 PM8/13/19
to elixir-lang-core
I like this proposal, it is simple and provides for more expressiveness while still being very clear to read. Why create a named function if you only use it in one place? Additionally, being able to capture values from the surrounding scope is a great way to avoid having to pass them all through, which can create noisy code unrelated to the actual functionality you are trying to implement.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-l...@googlegroups.com.

Christopher Keele

unread,
Aug 13, 2019, 1:29:27 PM8/13/19
to elixir-lang-core
1. Can we focus on the pros and cons of what was proposed?

I like this proposal, and would get a lot of use out of the implementation proposed.

The name Function.pipe_into/2 is context-specific to being paired with the pipe operator. An alternative name such as Function.into/2 or Function.inject/2 scans better in my head.

Łukasz Niemier

unread,
Aug 13, 2019, 2:00:44 PM8/13/19
to elixir-lang-core
 I have prepared small example of macro that does this already:

defmodule F do
  defmacro pipe_to(pipe, {function, _env, args}) when is_atom(function) and is_list(args) do
    func_arg = quote do: a
    args =
      for arg <- args do
        case arg do
          {:_, _, _} -> func_arg
          _ -> arg
        end
      end

    quote do
      (fn unquote(func_arg) -> unquote(function)(unquote_splicing(args)) end).(unquote(pipe))
    end
  end
end

It is not perfect yet, as for example this do not allow `Mod.func()` calls, but is is working pretty well as is.

Wiebe-Marten Wijnja

unread,
Aug 13, 2019, 5:34:17 PM8/13/19
to elixir-l...@googlegroups.com

On 13-08-2019 11:03, José Valim wrote:


[...] if I have experience with other functional programming languages, I would expect to be able to write this:

def find_foos(string) do
  fun = &Regex.scan(~r/foo/, &1)
  string
  |> String.upcase()
  |> fun
end

But that won't work, it has to be "fun.()". So I think we need to work consistently with calls. Allowing something that is not a call will be confusing.

This is a good counter-argument. 👍Maybe it is possible to super-charge the &-macro to even work in those cases, but it might become too abuse-enabling if we do.

Playing devil’s advocate a little, however: what would you expect to happen when someone does the same thing with Function.pipe_to (or whatever name is decided upon)?:


def find_foos(string) do
  fun = Function.pipe_to(&Regex.scan(~r/foo/, &1))
  string
  |> String.upcase()
  |> fun
end

~Marten/Qqwy

signature.asc

Allen Madsen

unread,
Aug 13, 2019, 7:51:57 PM8/13/19
to elixir-l...@googlegroups.com
This wouldn't be possible:

def find_foos(string) do
fun = Function.pipe_to(&Regex.scan(~r/foo/, &1))
string
|> String.upcase()
|> fun
end

It's missing the first argument of Function.pipe_to/2. You'd have to do something like:

def find_foos(string) do
fun = &Function.pipe_to(&1, &Regex.scan(~r/foo/, &1))
string
|> String.upcase()
|> fun.()
end

Though, I'm not sure what happens when there's shadowing of anonymous variables.



--
You received this message because you are subscribed to the Google Groups "elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-co...@googlegroups.com.

Allen Madsen

unread,
Aug 13, 2019, 8:10:48 PM8/13/19
to elixir-l...@googlegroups.com
In case it's helpful to see. The function implementation of this would likely be something like this:

defmodule Function do
def pipe_to(val, fun) when is_function(fun, 1) do
fun.(val)
end
end

Khaja Minhajuddin

unread,
Aug 15, 2019, 12:44:42 PM8/15/19
to elixir-lang-core
I definitely see the value of having a new function which helps with piping into anonymous functions. I vote for `Function.into`, it reads nicely and doesn't have the confusion of `pipe_to`.


On Tuesday, August 13, 2019 at 12:12:13 PM UTC-4, José Valim wrote:
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-l...@googlegroups.com.

Matt Baker

unread,
Aug 16, 2019, 11:31:40 AM8/16/19
to elixir-lang-core
I like this proposal.
  • It doesn't add any new syntax constructs or special cases to the language, correct? I know opinions vary, but I think this is a strength of the proposal.
  • To me "&1" happens to also serve as a convenient visual indicator of "where the value's gonna go." It's subjective, but for me it's just as effective as the idea of using "_" that some people have mentioned, while not changing or adding to the meaning of "_".
  • I found it quite intuitive
  • I also found it more clear than the existing (&foo..).() I see today. I've never written code this way and found it a little odd to read at first, but have since seen in in other code bases.
My only quibble is the name. pipe_to makes sense, but it also feels like it describes the use case more than describing what the function does. Am I correct in thinking this simply takes the first argument and passes it to the function capture on the right when it executes it?

A few of these have been floated already, but words that come to mind...
  • Function.call
  • Function.execute
  • Function.run
  • Function.dispatch
  • Function.apply
  • Function.send
All that said, I'd hate to see naming alone get in the way of the proposal, I'm in favor of it even as is.

Wiebe-Marten Wijnja

unread,
Aug 18, 2019, 4:02:27 PM8/18/19
to elixir-l...@googlegroups.com
The more I think about this proposal, the more I like it as well.

These last couple of days I have consciously encountered multiple
situations in which I realized that I was writing a pipeline in which
`Function.into` (or whatever name ends up being picked) would improve
readability.

~Marten / Qqwy

signature.asc

Kelvin Raffael Stinghen

unread,
Aug 20, 2019, 5:59:04 PM8/20/19
to elixir-lang-core
I'm really liking the proposal too. About the naming problem, my considerations:

- I agree with Matt when he says `pipe_to` makes sense, but it feels like it describes more of a use case then what the function does
- `call`, `execute`, `run` and `apply` look like they would take a function as the first argument, not the args. You can refer to plain english to get my reasoning: "to `call`/`execute`/`run`/`apply` the `function` with/to some `args`"
- `apply` by itself has another downside, because there is already `Kernel.apply`, which does basically what we need, but with the reverse argument order
- `send` could generate confusion because there is already `Kernel.send`, but so far for me, that's one of the best options
- `dispatch` is not very commonly used name, but that's not a very strong argument IMO, tied with `send` as the best option

So, I'm pro calling it `send` or `dispatch` because they correctly describe the function, not just an use case, and they are semantically correct: "to `send` or `dispatch` the `args` to the `function`"

Something that came to my mind is that we could have both options: `Function.send(args, function)` and `Function.dispatch(first_arg, function)`. That way, with `[1, 2] |> Function.send(&Kernel.+/2)`, would be possible to add multiple args functions to pipes (not really readable, but interesting technique IMO).

WDYT?

Kelvin Raffael Stinghen

unread,
Aug 20, 2019, 6:01:07 PM8/20/19
to elixir-lang-core
Forgot about `into`, IMO it does not describe the action that is being done, I mean, it's even hard to try to think about an english sentence that makes sense: "put the `args` `into` the `function`"?

Christopher Keele

unread,
Aug 20, 2019, 6:21:47 PM8/20/19
to elixir-l...@googlegroups.com
What I see as happening is that you are injecting an argument into a partially applied function call. Perhaps a two-word signature with something more explicit makes sense, like Function.inject_arg(arg, callable).

--
You received this message because you are subscribed to a topic in the Google Groups "elixir-lang-core" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/elixir-lang-core/JVFGZm58Qo0/unsubscribe.
To unsubscribe from this group and all its topics, send an email to elixir-lang-co...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/83e76b6e-32e1-4d92-8e52-29a218cbba28%40googlegroups.com.

Ivan Ivanov

unread,
Aug 21, 2019, 7:56:05 AM8/21/19
to elixir-lang-core
The `Function` module was introduced not long ago as there was no good place to host the functions that are currently in there. Having a function with this particular behavior into the `Function` module seems weird to me. Was having `Pipe` module considered at any point? Examples: `Pipe.to` or `Pipe.into`:
 
           "foo"
|> String.upcase()
|> Pipe.to(&Regex.scan(~r/foo/, &1)

One issue I have with having such function in the `Function` module is that it will be inconsistent with most of the code - the functions in `Enum` take the enumerable as the first argument, same goes for `Map`, `List`, `String`, `Tuple` and so on.

There is no `Pipe` struct or type and when working with pipes you already know that a value (first argument) is piped into a function (second argument), so having such parameters ordering in the `Pipe` module is logical






OvermindDL1

unread,
Aug 21, 2019, 12:03:50 PM8/21/19
to elixir-lang-core
Oh hey, `Pipe.to` is the first name I've seen that I like, and putting such things in a `Pipe` module does make argument ordering explicit, and it provides a module to put later pipe related things in too (such as perhaps something to do something when an ok tuple or a raw value but pass an error tuple or error atom or exception or so on past?)

José Valim

unread,
Aug 21, 2019, 12:07:24 PM8/21/19
to elixir-l...@googlegroups.com
It is unlikely we will add a new module for this functionality though. If it cannot fit one of Elixir's built-in modules, then it is best done as a package (which is most likely already done at this point).



José Valim
Skype: jv.ptec
Founder and Director of R&D

--
You received this message because you are subscribed to the Google Groups "elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-co...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/b08a7b47-8a49-4add-a924-be4a50a659a9%40googlegroups.com.

Floating Front

unread,
Aug 21, 2019, 6:59:43 PM8/21/19
to elixir-l...@googlegroups.com
How about 

Function.sink  

That name doesn’t seem to have been mentioned and might therefore not have an overloaded meaning (my knowledge here is though seriously lacking). 

It's  descriptive, as there is a source, pipe, and the flow ends up being swallowed by the sink :-)

R.
Fridrik.

Reply all
Reply to author
Forward
0 new messages