Ankhers

Ankhers

Yet another pipe to nth argument discussion

Just a little information upfront. Generally speaking, if I feel like I need to either break a pipe chain or use an anonymous function in order to pipe into an arbitrary argument, I will create a named function instead. My code becomes a bit more verbose, but I think it often comes with additional clarity. With that said, Lets get on with the discussion.


There was recently another proposal in order to pipe into an arbitrary argument of a function. I’m not really here to discuss the validity of that proposal. That proposal did get me to think about a potential different approach (that may or may not have come up in the past, please point me to it if it was brought up) that may get us (hopefully) 80% of what people are looking for.

When I see these discussions crop up, I realize that people are not showing “real” code most of the time and are just coming up with toy examples to show off the proposed syntax. With that said, it appears that people want to pipe into the second parameter more frequently (again, it may just be toy examples though).

So I am mostly here to ask, if you had to take a guess (and if you have some time, maybe scour a codebase or two), what percentage of time are you trying to pipe to the 2nd argument vs 3rd+. I’m not really interested in piping to the first, because we already have a working solution for that.

If it turns out that most of the time people are looking to pipe to the 2nd argument, Maybe we could implement flip/2. The first argument is the value you want to become argument 2 of the second argument (that is comfusing…). Lets take a look at a couple examples

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

map
|> Map.get(:foo)
|> process()
|> flip(GenServer.call(pid, 10_000))

I got the idea from Haskell, though I’m sure something similar is in many languages. We are unable to do exactly the same thing because currying is not really a thing by default in Elixir. Haskell’s version is actually (a -> b -> c) -> b -> a -> c, which roughly transaltes to the first argument being a function that takes 2 arguments, the secound argument is some value b and the third argument being some value a. Roughly translated to Elixir could look something like fn (fun, b, a) -> fun.(a, b) end

flip/2 would really only be intended to be used inside a pipe chain, because writing flip("foo", Regex.scan(~r/foo/)) is not as clear as Regex.scan(~r/foo/, "foo")

So if anyone has any thoughts on flip/2, I would love to hear them. And if anyone can either go through some code, or take a guess at percentages for 2nd vs above 2nd argument for pipes, it would be appreciated.

Also, if you would like to test it out in some code, I threw the flip package up onto hex and you can find the code on github.

Most Liked

OvermindDL1

OvermindDL1

Oh there is already a library out somewhere that adds the _ ability to pipes, forgot it’s name, but it’s really really easy to code, let me whip up an untested and probably somehow wrong example in the console:

╰─➤  iex
Erlang/OTP 21 [erts-10.2.1] [source] [64-bit] [smp:6:6] [ds:6:6:10] [async-threads:1] [hipe]

Interactive Elixir (1.8.1) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> defmodule BetterPipe do  
...(1)>   def better_piping(value, call, i) do
...(1)>     # Obviously come up with a better reusable binding name, purely temporary and probably still not scope clean in some cases 
...(1)>     binding = Macro.var(:"$bpb$#{i}", __MODULE__)
...(1)>     {call, n} = Macro.prewalk(call, 0, fn
...(1)>       {:_, _meta, ctx}, n when is_atom(ctx) -> {binding, n + 1}
...(1)>       {:|>, _meta, [v, c]}, n -> {better_piping(v, c, i + 1), n}
...(1)>       ast, n -> {ast, n}
...(1)>     end)
...(1)>     cond do
...(1)>       n == 0 -> Macro.pipe(value, call, 0)
...(1)>       n == 1 -> Macro.prewalk(call, fn ^binding -> value; ast -> ast end)
...(1)>       n -> quote do unquote(binding) = unquote(value); unquote(call) end
...(1)>     end
...(1)>   end
...(1)> 
...(1)>   defmacro value |> call do
...(1)>     BetterPipe.better_piping(value, call, 0)
...(1)>   end
...(1)> 
...(1)>   defmacro __using__(_) do
...(1)>     quote do
...(1)>       import Kernel, except: [|>: 2]
...(1)>       import BetterPipe, only: [|>: 2]
...(1)>     end
...(1)>   end
...(1)> end
{:module, BetterPipe,
 <<70, 79, 82, 49, 0, 0, 11, 108, 66, 69, 65, 77, 65, 116, 85, 56, 0, 0, 1, 96,
   0, 0, 0, 36, 17, 69, 108, 105, 120, 105, 114, 46, 66, 101, 116, 116, 101,
   114, 80, 105, 112, 101, 8, 95, 95, 105, 110, ...>>, {:__using__, 1}}

And it is thus used as:

iex(2)> use BetterPipe
BetterPipe
iex(3)> "test" |> String.upcase() # Normal style works
"TEST"
iex(4)> "test" |> String.upcase(_) # Or use a hole
"TEST"
iex(5)> "test" |> IO.inspect(_, label: _) # Or use many holes, even inside other expressions
test: "test"
"test"
iex(6)> "test" |> IO.inspect(_, label: _) |> String.upcase(_) |> IO.inspect() |> String.downcase() |> IO.inspect(_, label: _)
test: "test"
"TEST"
test: "test"
"test"
iex(13)> ["1", "22", "333"] |> Enum.find(_, fn v -> v |> Regex.match?(~R/^[\d][\d]$/, _) end) # Recursive is no problem either
"22"

Supports full multi-positioning, etc… They are simple to write (although elixir’s language has fun with corner cases, needs tests!). And it is so much more clear to me, no magical values being stuffed into positions (which is weird in a non-curried language, does it go to the start, end, middle, what?!)!

Ah right, it was your library!

Why only one? And it doesn’t seem to support piping into a structure in an argument either, or just a structure outright?

With mine above for example:

iex(14)> 1 |> %{a: _}
%{a: 1}
iex(15)> [a: 42] |> %{a: _[:a]}
%{a: 42}

It is generic piping into something, not just function calls! :slight_smile:

I really should publish this sometime, with tests and all, I keep remaking it almost verbatim every few months… >.>

10
Post #5
OvermindDL1

OvermindDL1

Flip is nice when the language has auto partial application (no curry’ing needed), but Elixir does not have that, mostly owing to lacking a typing system, thus flip becomes too difficult to read.

I’m still personally a fan of just placing a _ as a “hole” (both visually and logically) for the pipe to fill, which would allow you to do things like:

something
|> blah(42, _)
|> blorp(_) # I like being explicit, no magical 'place in front' magicalness
|> vwoop.("test", _, "thing", _) # Can even be used multiple times!

But it is a nice obvious hole, both visually (“Oh, this is a piped into!”) and logically (scan the AST for _ and just replace it with a binding to the prior expression, single line of work).

taiansu

taiansu

Actually I wrote a plugin named pipe_to which makes it possible to specified the position of argument while pipe, but only one argument for now.

Where Next?

Popular in Discussions Top

arcanemachine
https://nitter.net/josevalim/status/1744395345872683471 https://twitter.com/josevalim/status/1744395345872683471
New
Jayshua
I recently came across the javascript library htmx. It reminded me a lot of liveview so I thought the community here might be interested....
New
sashaafm
I’m trying to evaluate the best combo/stack for a BEAM Web app. Right now I’m exploring Yaws a bit, after having dealt with Phoenix for a...
New
cvkmohan
The upcoming Phoenix 1.6 release looks very interesting. Became a habit to watch the commits - and - what they are bringing in. phx.gen...
New
Nvim
Elixir appears to be a superior language to Python. I don’t see any advantage of Python over Elixir. Are there any?
New
WolfDan
After doing a port from a c++ library to my project in phoenix I’ve seen that I need a faster way to run this algorithm and I found this ...
New
fireproofsocks
I’ve been working on an Elixir project that has required a lot of scripting. I usually reach for Elixir because I like it more (and in th...
New
marciol
Please, let me know if this kind of discussion already took place in another topic . Hi all, how do you consider if is better to build ...
New
hazardfn
I suppose this question is effectively hackney vs. ibrowse but we are at a point in our project where we have to make a choice between th...
New
chulkilee
Here are the list of HTTP client libraries/wrappers, and some thoughts on HTTP client in general. I’d like to hear from others how they w...
New

Other popular topics Top

siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
chrismccord
Phoenix 1.4.0 released Phoenix 1.4 is out! This release ships with exciting new features, most notably with HTTP2 support, improved deve...
688 31194 112
New
josevalim
Hi everyone, One of the features added to Elixir early on to help integration with Erlang code was the idea of overridable function defi...
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New
AstonJ
Please see the new poll here: Which code editor or IDE do you use? (Poll) (2022 Edition) It’s been a while since we first asked this, I...
208 31307 143
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
Brian
What is the proper way to load a module from a file in to IEX? In the python world, doing something like this pretty standard: from ....
New
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New
JakeBecker
TL;DR: I’ve just released an implementation of Microsoft’s IDE-independent Language Server Protocol for Elixir. It adds language support ...
1144 54250 245
New

We're in Beta

About us Mission Statement