how does |> pipe operator in elixir work?
is this all similar to using
|
operator on unix?
Not exactly. Consider the following example:
echo 'foo' | echo 'bar'
#⇒ bar
With an Elixir pipe operator, we were to get foo bar
string printed out.
If you want the comparison against unix pipe, it’s more like | xargs
, save for xargs
will append the standard input to the command given (append the output of the previous command, when used after pipe,) while Elixir pipe operator will prepend the output of the previous command.
what is the big deal here about this operator compared to traditional way of passing first argument
Well, it’s a matter of maintaining readable code. Consider the following task: one should receive parameters from the standard input, validate them, possibly coerce them to the respective types and then perform an action, providing these arguments as an input. In Elixir we’d write this using pipe operator:
input
|> validate(ValidatorEngine)
|> coerce(to: [:int, :float])
|> perform
Without this operator, the exactly same code would look like:
perform(coerce(validate(input, ValidatorEngine), to: [:int, :float]))
What is more readable, maintainable and, after all, elegant?
in any functional language like in Pascal or C
Neither Pascal
nor C
are functional. These languages are imperative. Those two are related as “having fun” compared to “having functions”.
can we pass variable argument using this way?
It’s easy to check in Elixir codebase: Macro.unpipe
that unpipes the foo |> bar |> baz
notation has no magic inside. Hence, no, one can not just pipe as many arguments as they wanted. Whenever it’s needed, one uses tuples/lists/maps structures to wrap the output of the previous command to the single term.
The main benefit of the pipe operator is that instead of calling multiple functions in a nested fashion
Enum.join(Enum.map(String.split("hello, world!", " "), &String.capitalize/1), " ")
or having many intermediate "throw-away variables"
string = "hello, world!"
words = String.split(string, " ")
capitalized_words = Enum.map(words, &String.capitalize/1)
Enum.join(capitalized_words, " ")
you can use the pipe operator to write
"hello, world!"
|> String.split(" ")
|> Enum.map(&String.capitalize/1)
|> Enum.join
Most notably, arguments are now very close to the function that receives them. Moreover, the order of the function invocations in the code resembles the order of executions - read from top to bottom instead from the inside out. Finally, not having the unnecessary "throw-away variables" reduces noise.
Not only does it make your code easier to read, it also tends to positively influence how you design your APIs. It encourages you to think about your code as a series of transformations on data, which leads to very clean solutions in many cases.
Also, can we pass variable argument using this way?
No, you cannot pass multiple arguments in this way - although you could use a tuple for example, to pass multiple values via a single argument.