0

I will like to know why we need to add .() when using the pipe operator, if the function is not called and receive only one argument?

id = &(&1)

"Hello" |> id.() |> upcase # HELLO

Expected:

id = &(&1)

"Hello" |> id. |> upcase # "undefined function String.upcase/0"

Why doesn't it work? I would like to have an explanation on how Elixir behave.

Jonathan Dion
  • 1,651
  • 11
  • 16
  • I realize that English may not be your first language but I'm not following you on the "Expected" part of your question. You're expecting the "undefined function String.upcase/0" when you call `id.`? – Onorio Catenacci Dec 27 '17 at 13:12
  • 1
    By the way, @JoseValim gave a great explanation for why we sometimes need the parentheses a while ago. You may want to take a look at this [Q & A](https://stackoverflow.com/questions/18011784/why-are-there-two-kinds-of-functions-in-elixir) – Onorio Catenacci Dec 27 '17 at 13:18
  • Thanks, @OnorioCatenacci. I just don't understand why `id.` is not valid and we need to "call it": `id.()`. I'm from a JS background and when we call a function like `id()` the function is being executed. I hope it makes sense. – Jonathan Dion Dec 27 '17 at 14:14

1 Answers1

6

There are no “methods defined on objects” in Elixir. Modules have functions. To call a function, one should call it as String.upcase:

iex> id = & &1
iex> "Hello" |> id.() |> String.upcase()
#⇒ "HELLO"

If you insist on calling upcase without using it’s fully-qualified name, do import String, only: [upcase: 1] upfront:

iex> import String, only: [upcase: 1]
iex> "Hello" |> id.() |> upcase()
#⇒ "HELLO"

Anonymous functions can not be used in the pipe chain since they are literally anonymous. Binding the anonymous function to the local variable does not make it “pipeable,” and the explicit function call is needed. The same way one pipes to the actual call of the String.upcase/1, not to “variable it’s bound to.” The following won’t work without an explicit call with .() too:

iex> id = &String.upcase/1
iex> "hello" |> id

while this works:

iex> id = &String.upcase/1
iex> "hello" |> id.()

One might always see the difference by checking the respective AST:

iex> quote do:  "hello" |> id
#⇒ {:|>, [context: Elixir, import: Kernel], ["hello", {:id, [], Elixir}]}
iex> quote do:  "hello" |> id.()
#⇒ {:|>, [context: Elixir, import: Kernel],
#   ["hello", {{:., [], [{:id, [], Elixir}]}, [], []}]}
Aleksei Matiushkin
  • 119,336
  • 10
  • 100
  • 160
  • Thanks. I forgot to use the `quote` to have the AST. Make more sense now. If I do understand, we need to use `.()` to "bind" the function to make it "pipeable"? – Jonathan Dion Dec 26 '17 at 18:02
  • 1
    We need to use `.()` to call the function. For named function `b`, `a |> b` works because `b` is the function name and `b a` is a legit (though not recommended and issuing warnings) syntax for `b(a)`. Anonymous functions are _anonymous_, they have to be called with `b.(a)`. – Aleksei Matiushkin Dec 26 '17 at 18:17
  • I think I'm just confused less say I have `[1, 2, 3] |> List.first()` and `[1, 2, 3] |> List.first`. Calling `List.first()` should immediately execute the function and throw an error no? – Jonathan Dion Dec 26 '17 at 19:55
  • Not at all. Both are exactly same. Pipe operator always takes precedence and passes LHO as the very first param to RHO which must be a call to the function. – Aleksei Matiushkin Dec 26 '17 at 20:15