…unless you want someone else to understand your code
You have to admit that the first bit of code tells us what’s going on (since the variable names help us to make that determination). It’s not immediately clear in the second version of code above that what is written to the screen is a total of the two; it might take longer to figure this out.
def total_pieces_on(board) do
{ black, white } = Go.Board.piece_count(board)
"Total pieces: #{black + white}"
end
Presumably this version still works, drops a local variable but keeps just enough in place to inform the next coder what just happened.
The syntax in Elixir is a bit different than in some other common languages. It gets a lot more readable if you move the anonymous functions into separate functions.
Anonymous functions get called with a dot:
# define an anonymous function
f = fn x, y -> x + y end
# the dot is required
f.(3, 4)
#=> 7
The &1 part is explained here. It’s just capturing the first argument, which is the sum of black + white.
You can make it more readable by moving the anonymous functions into the private section. Then the piped section reads more like English.
defmodule Game do
@doc """A more readable example."""
def total_pieces_on(board) do
board
|> count_pieces_by_color()
|> sum_all_pieces()
|> format_sum()
end
# private functions
defp count_pieces_by_color(_board), do: { 10, 11 } # skipping the computing of pieces
defp sum_all_pieces({ black, white }), do: black + white
defp format_sum(total_pieces), do: "Total pieces: #{total_pieces}"
end
Game.total_pieces_on("In a real program, this could be game board data.")
#=> "Total pieces: 21"
It’s possibly a contrived example to explain the concept. This latest version reasonably explains what’s happening by choosing meaningful function names. We assume that the piping mechanism is similar to the dotted notation in JavaScript: (new Date()).setMinutes(0,0,0) in that the output of one is the input of the next.
Not sure what happens if count_pieces_by_color() returns something unexpected to the next function like “hello world!”. Does it break gracefully, is it silently forgiving in a bad way?
Pipes (|>) use the return value of the last function as the first argument of the next function.
So these are the same:
Enum.map([1, 2, 3], fn n -> n * 2 end)
#=> [2, 4, 6]
[1, 2, 3]
|> Enum.map(fn n -> n * 2 end)
#=> [2, 4, 6]
Fault tolerance is one of the main features. This playlist has a good example of a program that has a function that is designed to crash repeatedly, but the program still completes all of its tasks successfully.