They remove loops in the Erlang abstract machine so that there are only ever O(1) instructions executed between each function call (where a tail-recursion is considered a function call.) With this constraint in place, the runtime can get away with a hybrid of cooperative and preemptive scheduling called “reduction scheduling”, where functions only ever yield when they make a function call (or return from one.)
By ensuring every function body has O(1) “reductions” before it hits a CALL or RET op, the runtime guarantees (unlike cooperative scheduling) that execution will always yield from a task in a bounded amount of time; and by ensuring yields only happen at call-sites, the runtime ensures that (unlike preemptive scheduling) there is no register state to preserve at time of yield—all the scheduler has to record when it pauses a task is the thunk (function pointer + parameter list) for the call it was about to make.
Together, these guarantees allow for extremely low-overhead context switching between tasks in a soft-realtime context.
Other systems (e.g. .NET’s Orleans) simulate this hybrid approach by splitting code into state machines with explicit yield points being the state transitions. But AFAIK, only ERTS takes the approach of making function calls into the state transitions. (Because, without the no-native-loops constraint, such an approach doesn’t work at all.)
I think it's important to realise that they're different ways of writing the same thing. For example, in Lisp, the 'do' operator is basically a C-style for loop: in Scheme, it's a macro over tail recursion, and in Common Lisp, it's a macro over gotos, but it provides an almost identical interface to programmers. Using recursion over loops is just a stylistic/syntactic choice, the really important part is understanding how recursive functions are equivalent to loops, and vice versa, so that you can apply your experience in one situation to the other.
Enum.map(1..100, &IO.puts/1)
prints the numbers 1 to 100
for i <- 1..100, do: IO.puts(i)
I guess what people mean is that that’s an abstraction over function calls?
I doubt it would be for someone from a Ruby background, where higher order functions already generally replace built-in loops.