For syntax for pipes - proposal

Proposed syntax

[:my, :list] |> for x do
  IO.puts(x)
end

Seems natural since the following forms exist:

my_var |> case do ... end
my_cond |> if do ... end
1 Like

Maybe list comprehension?

for x <- [:my, :list], do: IO.puts(x)

This can also be achieved with

[:my, :list] |> Enum.each(&IO.puts/1)
2 Likes

Yes, I want an alternative form for list comprehension, just like |> case do and |> if do are the alternative forms for case and if

It’s probably a bit more difficult to implement than case. How would you express

for x <- [1, 2, 3], y <- [2, 3, 4], x != y do
  # ...
end

with a pipe?

And as @kokolegorille has already pointed out, simple for cases can be expressed with one of Enum.each, Enum.map, or Enum.reduce depending on the result that you want.

5 Likes

I do not propose and don’t have practical need for support of complex cases like you mention. It’s a shortcut anyway. Probably, there is still a reasonable syntax for that, like

[1, 2, 3] |> for x do
  [2, 3, 4] |> for y, x != y, do: ...
end

A simple solution would be to leave 2nd for in its original form:

[1, 2, 3] |> for x do
  for y <- [2, 3, 4], x != y, do: ...
end

That would defeat a lot of FP rules…

  • No loop, prefer Enum
  • No mutability
  • Lists are not like Python lists, they are linked lists
[1, 2, 3] |> for x do
  [2, 3, 4] |> for y, x != y, do: ...
end

that wouldn’t work, since after do a new statement begins.

Sorry, of course that doesn’t work

Leaving the simplest form then: list |> for x do

the richer syntax probably would be list |> for x, y <- list2 do end

Pretty it ain’t.

defmodule X do # X as in dangerously eXperimental

  defmacro pipe_for(enumerable, to, rest) do
    quote do
      unquote(
        {:for, [],
          [ {:<-, [], [to, enumerable]} | wrap_block(rest,[]) ]
        }
      )
    end
  end

  defp wrap_block([_] = block_list, gen_filters),
    do: :lists.reverse([block_list | gen_filters])
  defp wrap_block([gen_filter | rest], gen_filters),
    do: wrap_block(rest, [gen_filter | gen_filters])

end

defmodule Demo do
  import X

  def demo1() do
    [:my, :list]
    |> pipe_for(x, [do: (IO.puts x)])
  end

  def demo2() do
    other = [1,2]
    [:a, :b, :c]
    |> pipe_for(i, [j <- other, do: ({i,j})])
  end

  defp multiple_of_3?(n), do: rem(n,3) == 0

  def demo3() do
    0..15
    |> pipe_for(n, [multiple_of_3?(n), do: (n * n)])
  end

  def demo4(n) do
    1..n
    |> pipe_for(a, [
          b <- 1..n,
          c <- 1..n,
          a + b + c <= n,
          a*a + b*b == c*c,
          do: (
            {a, b, c}
          )])
  end

  def demo5() do
    other = [2,3,4]
    [1,2,3]
    |> pipe_for(x, [do: (
         other
         |> pipe_for(y, [x != y, do: (y)])
       )])
  end

end

IO.puts("-- demo1: ")
Demo.demo1()
IO.puts("-- demo 2: #{inspect Demo.demo2()}")
IO.puts("-- demo 3: #{inspect Demo.demo3()}")
IO.puts("-- demo 4: #{inspect Demo.demo4(48)}")
IO.puts("-- demo 5: #{inspect Demo.demo5()}")
 $ elixir demo.exs
-- demo1: 
my
list
-- demo 2: [a: 1, a: 2, b: 1, b: 2, c: 1, c: 2]
-- demo 3: [0, 9, 36, 81, 144, 225]
-- demo 4: [{3, 4, 5}, {4, 3, 5}, {5, 12, 13}, {6, 8, 10}, {8, 6, 10}, {8, 15, 17}, {9, 12, 15}, {12, 5, 13}, {12, 9, 15}, {12, 16, 20}, {15, 8, 17}, {16, 12, 20}]
-- demo 5: [[2, 3, 4], [3, 4], [2, 4]]
$ 

Don’t look at me, Elixir let me do it …

8 Likes

I just think we are putting pipes everywhere, where they aren’t needed, there is no need to put it on expressions, or even on one function without composition.

for x <- bar is much more convenient as other languages apply the same syntax.
maybe |> case do seems not lexical at all, case what?
myvar |> Enum.each(fn x -> x |> case do ... end end) omg.

Soon we are in the same boat as Scala, having N ways to write the same thing.

Just don’t overuse the poor pipe.

3 Likes

can’t we achieve the same thing with Enum.reduce_while/3 ?

I personally don’t like it. Pipe to me says it is being inserted into the thing it points at. In this case I would expect x to be the entire list. list comprehension is much more logical to look at as for x <- [list] I read that as for x in list.