373274 (2) [Avatar] Offline
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, [])

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

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

defp generate_from_range(from, to, list) do
  generate_from_range(from, to - 1, [to | list])
sjuric (86) [Avatar] Offline
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
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
Thanks, I'm really glad you like it so far smilie
sjuric (86) [Avatar] Offline
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
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, [])

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

  defp do_range(from, to, dir, acc) do
    if from == to do
      do_range(from, to - dir, dir, [to|acc])
sjuric (86) [Avatar] Offline
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).