With both square and add_1, all the work is done in the second function definition. And that definition looks about the same for each—it returns a new list whose head is the result of either squaring or incrementing the head of its argument and whose tail is the result of calling itself recursively on the tail of the argument. Let’s generalize this. We’ll define a function called map that takes a list and a function and returns a new list containing the result of applying that function to each element in the original.
def map(, _func), do: 
def map([ head | tail ], func), do: [ func.(head) | map(tail, func) ]
The map function is pretty much identical to the square and add_1 functions. It ...