"Piping Partials" or "Implicit Argument" Proposal

244 views
Skip to first unread message

Cristian Garcia

unread,
May 5, 2016, 11:18:41 AM5/5/16
to elixir-lang-talk
I just wrote this code

items = map
      |> Enum.sort_by(&elem(&1, 1))
      |> Enum.map(&elem(&1, 0))

and though if I should rewrite is as a lambda because maybe I wouldn't understand it in the future. Then I started thinking how you would do it in Elm, and the answer was just (if those same functions existed)

items = map
      |> Enum.sort_by (elem 1)
      |> Enum.map (elem 0)
 
Its much more readable, the &1 from Elixir is just too noisy, so I thought why can't we just remove it and let it behave like those functions in pipes? Then the idea didn't seem so bad, if we assume this equality

&some_fun(a, b, ...) == &some_fun(&1, a, b, ...)

Just like in pipes, the first argument is implicit, it would just be a special case for the & partial operator where there are no &# present. Rewritten the Elixir code looks like this

 items = map
      |> Enum.sort_by( &elem(1) )
      |> Enum.map( &elem(0) )

Added some spacing for clarity but its much more readable, if you could make the inner parenthesis optional thing would go really smooth. There are a ton of example I run into, look how much readable something like this becomes

words_with_a = 
  filename
  |> File.stream!
  |> Stream.flat_map(&String.split(" "))
  |> Stream.filter(&String.contains("a"))

 

José Valim

unread,
May 5, 2016, 11:28:23 AM5/5/16
to elixir-l...@googlegroups.com
In Elm you have currying, Elixir does not. That's why the solutions are different.

I think adding &... without &1 would just make everything more confusing. Specially because everyone would expect "&add(1, 2)" to be "fn -> add(1, 2) end" and not "fn x -> add(x, 1, 2) end".

Just like in pipes, the first argument is implicit

Arguments are not implicit in pipe, it is on the left-side of the pipe. Which is completely different than disappearing with it altogether. :)





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-talk" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-ta...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-talk/0a3f2be5-8ab7-43f5-92ac-bda9fb535022%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Cristian Garcia

unread,
May 5, 2016, 11:40:04 AM5/5/16
to elixir-lang-talk
Jose,

How about a "&>" operator? The idea is to make partials more readable then doing what in other functional languages would do by currying

words_with_a = 
  filename
  |> File.stream!
  |> Stream.flat_map(&> String.split(" "))
  |> Stream.filter(&> String.contains("a"))

You could even go further are remove the argument from the previous example 

get_words_with_a =
  &> File.stream!
  &> Stream.flat_map(&> String.split(" "))
  &> Stream.filter(&> String.contains("a"))

José Valim

unread,
May 5, 2016, 11:43:33 AM5/5/16
to elixir-l...@googlegroups.com
I honestly do not believe the few characters it saves are worth it in terms of complexity it brings when reading code.



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-talk" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-ta...@googlegroups.com.

Cristian Garcia

unread,
May 5, 2016, 11:52:57 AM5/5/16
to elixir-lang-talk
Just notice that the second example required function composition and it would not work. But the first example would be a lot more readable


words_with_a = 
  filename
  |> File.stream!
  |> Stream.flat_map(&> String.split(" "))
  |> Stream.filter(&> String.contains("a"))

vs

words_with_a = 
  filename
  |> File.stream!
  |> Stream.flat_map(&String.split(&1, " "))
  |> Stream.filter(&String.contains(&1, "a"))

I think it improves readability and people can reuse their model on how they use pipes to use this operator. 

On Thursday, May 5, 2016 at 10:18:41 AM UTC-5, Cristian Garcia wrote:

Lorenzo Bolla

unread,
May 6, 2016, 4:37:48 AM5/6/16
to elixir-lang-talk
Maybe you can implement your own "curry"?

    iex(11)> [{:c, 3}, {:a, 1}, {:b, 2}] |> Enum.sort_by(&elem(&1, 1))
    [a: 1, b: 2, c: 3]

   iex(17)> curry = fn (f, args) -> (fn x -> apply(f, [x | args]) end) end
   #Function<12.50752066/2 in :erl_eval.expr/5>

   iex(18)> [{:c, 3}, {:a, 1}, {:b, 2}] |> Enum.sort_by(curry.(&elem/2, [1]))
   [a: 1, b: 2, c: 3]

Although, I am not sure it's prettier than "&elem(&1, 1)".

Alexei Sholik

unread,
May 6, 2016, 7:59:23 AM5/6/16
to elixir-l...@googlegroups.com
I just wrote this code
items = map
      |> Enum.sort_by(&elem(&1, 1))
      |> Enum.map(&elem(&1, 0))
and though if I should rewrite is as a lambda because maybe I wouldn't understand it in the future.

Just a suggestion: it would have been more readable if you wrote it as

items =
  map
  |> Enum.sort_by(fn {_, bar} -> bar end)
  |> Enum.map(fn {foo, _} -> foo end)

Of course, assuming that you replace the placeholders "foo" and "bar" with the names that describe what your tuple elements are. It is more verbose but objectively more descriptive: first, sort it by "bars" and then return just the "foos".

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

Lorenzo Bolla

unread,
May 6, 2016, 11:34:25 AM5/6/16
to elixir-lang-talk


On Friday, May 6, 2016 at 12:59:23 PM UTC+1, Alexei Sholik wrote:
I just wrote this code
items = map
      |> Enum.sort_by(&elem(&1, 1))
      |> Enum.map(&elem(&1, 0))
and though if I should rewrite is as a lambda because maybe I wouldn't understand it in the future.

Just a suggestion: it would have been more readable if you wrote it as

items =
  map
  |> Enum.sort_by(fn {_, bar} -> bar end)
  |> Enum.map(fn {foo, _} -> foo end)

Of course, assuming that you replace the placeholders "foo" and "bar" with the names that describe what your tuple elements are. It is more verbose but objectively more descriptive: first, sort it by "bars" and then return just the "foos".

But your suggestion is not equivalent to the OP's: it only works if the input list is made of tuples of exactly size 2 (instead of "at least" size 2).

    iex(1)> [{1, 2, 3}, {:a, :b}] |> Enum.sort_by(&elem(&1, 1))
    [{1, 2, 3}, {:a, :b}]

    iex(2)> [{1, 2, 3}, {:a, :b}] |> Enum.sort_by(fn {_, bar} -> bar end)
    ** (FunctionClauseError) no function clause matching in :erl_eval."-inside-an-interpreted-fun-"/1

L.

Alexei Sholik

unread,
May 6, 2016, 12:56:04 PM5/6/16
to elixir-l...@googlegroups.com
But your suggestion is not equivalent to the OP's: it only works if the input list is made of tuples of exactly size 2 (instead of "at least" size 2).

That's a nice observation.

I think it's unusual to have a list of tuples of different sizes. But even if that's really the case, it is one more reason to go with an anonymous function instead of the shorthand syntax, because then it would be more explicit/less surprising for anyone reading the code, even yourself a few months later.

items =
  map
  |> Enum.sort_by(fn
    {_, bar} -> bar
    {_, bar, _} -> bar
  end)
  |> Enum.map(fn
    {foo, _} -> foo
    {foo, _, _} -> foo
  end)

If your goal is to be concise, then you'll probably not like this solution. But to me it looks more like a problem with what is stored in the list than with the verbosity of the syntax.

I rarely use elem() because pattern-matching is more explicit and descriptive 100% of the time.

Cristian Garcia

unread,
May 15, 2016, 6:26:13 PM5/15/16
to elixir-l...@googlegroups.com
Since I was enumerating a Map, Alexei's suggestion works and its how I first though to write it but I wanted to be more concise. The concise version was however not very readable and got me thinking if Elixir could implement a curry operator.

Are there any free unary operators you can implement yourself? A "curry-pipe" operator might be nice for my personal projects.

eksperimental

unread,
May 15, 2016, 7:30:50 PM5/15/16
to elixir-l...@googlegroups.com

On Sun, 15 May 2016 22:25:20 +0000
Cristian Garcia <cgarc...@gmail.com> wrote:

> Since I was enumerating a Map, Alexei's suggestion works and its how I
> first though to write it but I wanted to be more concise. The concise
> version was however not very readable and got me thinking if Elixir
> could implement a curry operator.
>
> Are there any free unary operators you can implement yourself? A
> "curry-pipe" operator might be nice for my personal projects.

This was featured in Elixir Radar #39

Custom Infix Functions in Elixir
[ http://www.rodneyfolz.com/custom-infix-functions-in-elixir/ ]
Although Elixir does not support custom operators, it ships with a
handful of infix operators that are not used anywhere in the language.
Rodney Folz shows how to define and use them in your projects when
specific use cases may arise.

Cristian Garcia

unread,
May 16, 2016, 12:05:59 AM5/16/16
to elixir-lang-talk

How about unary prefix? Are there such definable operators?


--
You received this message because you are subscribed to the Google Groups "elixir-lang-talk" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-ta...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages