373274 (2) [Avatar] Offline
#1
I am new to Elixir, so any clarifications would be greatly appreciated.

For section 3.4.2, in the "Practicing" section, the range/2 example, wouldn't it be better to do a recursive tail from the 'to' to the 'from' value, as opposed to what was posted on https://github.com/sasa1977/elixir-in-action/blob/master/code_samples/ch03/recursion_practice_tc.ex that relies at the end on 'Enum.reverse'? Here is what a potential solution would look like:

def range(from, to) do
  generate_from_range(from, to, [])
end

defp generate_from_range(from, to, list) where from > to do
  list
end

defp generate_from_range(n, n, list) do
  [n | list]
end

defp generate_from_range(from, to, list) do
  generate_from_range(from, to - 1, [to | list])
end
sjuric (86) [Avatar] Offline
#2
You are indeed right!
Your solution with building backwards is still tail recursive without requiring the extra reversal pass.

I guess this example is a bit unfortunate, because I wanted to demonstrate the extra reversal technique. This approach is needed when you want to do tail recursion on an input where you can't cheaply walk backwards. An example can be seen in the function "positive" in the same module, where an input is a list which you need to iterate from head to tail.

In such cases you must either reverse the accumulated input after you're done, or go for non-tail recursion. The latter is usually fine, unless you expect a very large (possibly infinite) number of elements in the input.

I'll update the GitHub repo.

Thanks again, and congrats for the good catch! smilie
373274 (2) [Avatar] Offline
#3
Glad to hear. This is just a reflexion of how awesome this book has been at teaching the fundamentals.
Great job Sasa.
sjuric (86) [Avatar] Offline
#4
Thanks, I'm really glad you like it so far smilie
sjuric (86) [Avatar] Offline
#5
FYI, I have pushed the simplified solution to the repo and expanded comments a bit. The commit is here.

Thanks again for reporting this, and wish you a lot of fun with the rest of the book smilie
353563 (1) [Avatar] Offline
#6
With a simple trick, you can extend range/2 to support any range of integers:

defmodule Test do
  def range(from, to) when is_integer(from) and is_integer(to) do
    dir = if from == to, do: 0, else: signum(to - from)
    do_range(from, to, dir, [])
  end

  defp signum(number) do
    if number > 0, do: 1, else: -1
  end

  defp do_range(from, to, dir, acc) do
    if from == to do
      [to|acc]
    else
      do_range(from, to - dir, dir, [to|acc])
    end
  end
sjuric (86) [Avatar] Offline
#7
Great comment!

I'll leave the solution as it is for now, because the exercise was intended to be simple, so I deliberately didn't want to confuse people with two-way ranges. But I'll definitely consider making some changes here in the next edition, such as making two tasks out of it (forward-only range and two-way range).