A standard idiom in Scheme 'build-and-reverse' suggests you only reverse the list once, at the very end, when its reverse has been completely built (thus reducing the complexity down to O(N) from quadratic.)
So yes, you end up in a tail call to reverse
but the list should be built without doing it. Scheme has plenty of local recursive binding constructs.
But.
If you build a range starting with the largest value (that should be one greater than the last element to the list) you don't need to reverse it in the end, at each iteration step you decrease a counter and prepend its new value to those already accumulated:
(define (range n)
(let rng ((m (- n 1)) (ret-val '())) ; named-let is very useful for small local recursive closures
(if (< m 0) ; that original (<= n 0) check is also handled here
ret-val ; here, the result is returned; note we don't need to reverse it
(rng (- m 1) (cons m ret-val))))))
(display (range 10))
(newline)
prints
(0 1 2 3 4 5 6 7 8 9)
Or, to demonstrate the build-and-reverse, we can start with the lowest value:
(define (range-asc n)
(let rng ((m 0) (ret-val '()))
(if (= m n)
(reverse ret-val) ; since we started from zero, we need to reverse it
(rng (+ m 1) (cons m ret-val)))))
(Looks like I still remember/can recover some Scheme. :-O)