Prevent floating point overflows right before they happen - common-lisp

As an easy way to overflow a floating point (I'm using double floats in my code, so I'll do so here as well):
(setq *read-default-float-format* 'double-float)
(defun example-float-overflow (x)
(example-float-overflow (* x x)))
(example-float-overflow 4.4)
Very quickly, x grows larger and larger. Pretty soon it reaches 5.295234290518905e164 and overflows. Which, is that even a double float anymore?
Anyway, what is the best way to identify the point right before it overflows? Right now I'm doing something like:
(defun example-float-overflow-no-error (x)
(if (> (* x x) 1.0e20)
x
(example-float-overflow-no-error (* x x))))
(example-float-overflow 4.4)
=> 1.973525870240772e10
Note: I'm not actually interested in the result, but the rest of my code depends on it to run as many times as it can before overflowing.

Barmar suggested handling the floating-point-overflow condition just after the overflow occurs. This is a little bit than detecting when it is about to occur, but it's probably the easiest thing to do. For instance, here's an add function that adds just like +, except that if something goes wrong, you can use the use-value restart to provide a different value:
(defun add (&rest numbers)
"Add numbers with +, but with a USE-VALUE restart
available in case of an overflow (or other condition)."
(restart-case (reduce '+ numbers)
(use-value (value &optional condition) value)))
Then you can establish use-value restarts that can be used to provide a value if a call to add fails:
;; Attempt to some arithmetic, but with a handler bound that
;; will return 42 if an floating point-overflow occurs.
(handler-bind ((floating-point-overflow
(lambda (condition)
(use-value 42 condition))))
(+ 5 (add most-positive-double-float most-positive-double-float)))
;; |----------- this ends up evaluating to 42 ---------------|
;;|------------- and 42 + 5 is 47 --------------------------------|
;=> 47

Related

Common Lisp: Optimizing functions in SBCL

For 2D graphics I need to optimize my functions, but in SBCL I get lots of comments about SBCL not being able to inline arithmetic operations. I tried all sorts of declarations but it doesn't seem to make the compiler happy. Here is a simple example:
(defun test-floor (x div)
(declare (type single-float x)
(type (signed-byte 64) div)
(optimize (speed 3)))
(floor x div))
gives the following 4 notes below. I'm completely lost as #'floor is a builtin function. I tried to find information/tutorials on how to properly give the compiler hints in SBCL and didn't find the right information, so any info would be very appreciated! Unfortunately optimization in Common Lisp is quite unknown territory for me. I'm using SBCL 1.3.20 on a Linux machine.
; file: /tmp/file595dqU
; in: defun test-floor
; (FLOOR CL-FLOCKS::X CL-FLOCKS::DIV)
; --> MULTIPLE-VALUE-BIND MULTIPLE-VALUE-CALL TRUNCATE LET*
; ==>
; (SB-KERNEL:%UNARY-TRUNCATE/SINGLE-FLOAT (/ SB-C::X SB-C::F))
;
; note: forced to do full call
; unable to do inline float truncate (cost 5) because:
; The result is a (values integer &optional), not a (values
; (signed-byte 64) &rest
; t).
; --> MULTIPLE-VALUE-BIND MULTIPLE-VALUE-CALL TRUNCATE LET* VALUES - *
; ==>
; (SB-KERNEL:%SINGLE-FLOAT SB-C::RES)
;
; note: forced to do full call
; unable to do inline float coercion (cost 5) because:
; The first argument is a integer, not a (signed-byte 64).
; --> MULTIPLE-VALUE-BIND MULTIPLE-VALUE-CALL FUNCTION IF VALUES 1-
; ==>
; (- SB-C::TRU 1)
;
; note: forced to do generic-- (cost 10)
; unable to do inline fixnum arithmetic (cost 1) because:
; The first argument is a integer, not a fixnum.
; The result is a (values integer &optional), not a (values fixnum &rest t).
; unable to do inline fixnum arithmetic (cost 2) because:
; The first argument is a integer, not a fixnum.
; The result is a (values integer &optional), not a (values fixnum &rest t).
; etc.
; --> MULTIPLE-VALUE-BIND MULTIPLE-VALUE-CALL FUNCTION IF VALUES
; ==>
; (+ REM SB-C::DIVISOR)
;
; note: doing signed word to integer coercion (cost 20) from div, for:
; the second argument of generic-+
;
; compilation unit finished
; printed 4 notes
CL-USER>
When you call floor, you have to deal with different subtypes of numbers: the code divides a float by an integer (that may involve coercing the integer as a float), and then have to coerce the result back to an integer. This is the amount of work that must be done, correctly, and you are unlikely to bypass it if you don't restrict your input types.
If instead you use ffloor, then the primary result is a float (you can still round it to an integer later, when you really need it (e.g. convert to pixels coordinates)). The following code does not give compilation notes:
(defun test-floor (x div)
(declare (type single-float x)
(type fixnum div)
(optimize (speed 3)))
(ffloor x div))
You may even declare div as being a float, which would push the responsibility of providing appropriately typed values (and performing runtime checks) to the callers.
Note also that you should probably (declaim (inline test-floor)) before you define the function; that helps because then the compiler can put shortcuts in the code to avoid checking input parameter types and boxing results.
Edit:
The range of a float cover a large possible domain (due to the exponent): more densely packed near zero, more spaced towards infinity. Integer values are linearly spaced, but cover a smaller range with the same number of bits. So if you want to guarantee your output fits in a fixnum, you have to make sure the float in your inputs does not go outside the range of fixnum, too. I tried the following:
(defun test-round (x)
(declare (type (single-float #.(float most-negative-fixnum 0f0)
#.(float (/ most-positive-fixnum 2) 0f0)) x)
(optimize (speed 3)))
(round x))
I had to half the upper range of the floats because when you test:
(typep (round (coerce most-positive-fixnum 'single-float)) 'fixnum)
... it returns NIL. I don't have much time to see why this happens, but this depends on your implementation and architecture. Taking half the most positive fixnum ensures the value is low enough to be converted as a fixnum. Now, I have no more compilation notes.
(the same goes for (signed-byte 64)))
NB. Unlike in the above example, you should use deftype and avoid repeating the same declarations everywhere.
If you want to specify the return value of an expression you can use THE:
(the fixnum (1+ 3))
But you really want to make sure that the value is actually a fixnum. If you 'lie', then Lisp may believe you and you have unspecified runtime effects. SBCL may warn at compile time, but you really should take care of that. If you provide wrong types are return wrong types, data may get corrupted and/or Lisp may crash.
Another way to specify a return value is the FTYPE declaration:
For example a function ith may take an integer and a list as arguments. It returns an arbitrary type -> T or any subtype.
(declaim (ftype (function (integer list) t)
ith))
For example:
(the fixnum (+ (the fixnum a) (the fixnum b)))
There you need to be sure that:
a is an fixnum
b is an fixnum
the sum of a and b is also always a fixnum
Here this is easier, since the sum of a and b is definitely a fixnum:
CL-USER 3 > (let ((a 3) (b 12))
(the fixnum (+ (the (integer 0 10) a)
(the (integer 3 20) b))))
15
Lisp may check that at runtime and/or compile-time. The addition now can be a simple fixnum operation and does not need to deal with fixnum overflows and bignums. If you set the safety value to something low, then the runtime check may also be omitted. But: you should never call this code with the wrong types.
SBCL is claiming that it can’t optimise the call to floor because it isn’t sure the return value will be small enough to fit into a 64-bit integer.
CL-USER> (test-floor 1f25 1234)
8103727629894569426944 ;
0.0
CL-USER> (format nil “~b” *)
;; a long binary string
CL-USER> (length *)
73
A 73 bit Integer may be returned but does not fit into 64 bits.
Se also the SBCL manual:
4 Compiler
4.1.3 Understanding Compiler Diagnostics
6 Efficiency
Edit: after some searching I have found the transformation for floor. It is here. I reproduce it below:
(deftransform floor ((number divisor))
`(multiple-value-bind (tru rem) (truncate number divisor)
(if (and (not (zerop rem))
(if (minusp divisor)
(plusp number)
(minusp number)))
(values (1- tru) (+ rem divisor))
(values tru rem))))
So that explains what the compiler messages were talking about

How to test if two functions are the same?

I found a code snippet somewhere online:
(letrec
([id (lambda (v) v)]
[ctx0 (lambda (v) `(k ,v))]
.....
.....
(if (memq ctx (list ctx0 id)) <---- condition always return false
.....
where ctx is also a function:
However I could never make the test-statement return true.
Then I have the following test:
(define ctx0 (lambda (v) `(k ,v)))
(define ctx1 (lambda (v) `(k ,v)))
(eq? ctx0 ctx1)
=> #f
(eqv? ctx0 ctx1)
=> #f
(equal? ctx0 ctx1)
=> #f
Which make me suspect that two function are always different since they have different memory location.
But if functions can be compared against other functions, how can I test if two function are the same? and what if they have different variable name? for example:
(lambda (x) (+ x 1)) and (lambda (y) (+ y 1))
P.S. I use DrRacket to test the code.
You can’t. Functions are treated as opaque values: they are only compared by identity, nothing more. This is by design.
But why? Couldn’t languages implement meaningful ways to compare functions that might sometimes be useful? Well, not really, but sometimes it’s hard to see why without elaboration. Let’s consider your example from your question—these two functions seem equivalent:
(define ctx0 (lambda (v) `(k ,v)))
(define ctx1 (lambda (v) `(k ,v)))
And indeed, they are. But what would comparing these functions for equality accomplish? After all, we could just as easily implement another function:
(define ctx2 (lambda (w) `(k ,w)))
This function is, for all intents and purposes, identical to the previous two, but it would fail a naïve equality check!
In order to decide whether or not two values are equivalent, we must define some algorithm that defines equality. Given the examples I’ve provided thus far, such an algorithm seems obvious: two functions should be considered equal if (and only if) they are α-equivalent. With this in hand, we can now meaningfully check if two functions are equal!
...right?
(define ctx3 (lambda (v) (list 'k v)))
Uh, oh. This function does exactly the same thing, but it’s not implemented exactly the same way, so it fails our equality check. Surely, though, we can fix this. Quasiquotation and using the list constructor are pretty much the same, so we can define them to be equivalent in most circumstances.
(define ctx4 (lambda (v) (reverse (list v 'k))))
Gah! That’s also operationally equivalent, but it still fails our equivalence algorithm. How can we possibly make this work?
Turns out we can’t, really. Functions are units of abstraction—by their nature, we are not supposed to need to know how they are implemented, only what they do. This means that function equality can really only be correctly defined in terms of operational equivalence; that is, the implementation doesn’t matter, only the behavior does.
This is an undecidable problem in any nontrivial language. It’s impossible to determine if any two functions are operationally equivalent because, if we could, we could solve the halting problem.
Programming languages could theoretically provide a best-effort algorithm to determine function equivalency, perhaps using α-equivalency or some other sort of metric. Unfortunately, this really wouldn’t be useful—depending on the implementation of a function rather than its behavior to determine the semantics of a program breaks a fundamental law of functional abstraction, and as such any program that depended on such a system would be an antipattern.
Function equality is a very tempting problem to want to solve when the simple cases seem so easy, but most languages take the right approach and don’t even try. That’s not to say it isn’t a useful idea: if it were possible, it would be incredibly useful! But since it isn’t, you’ll have to use a different tool for the job.
Semantically, two function f and g are equal if they agree for every input, i.e. if for all x, we have (= (f x) (g x)). Of course, there's no way to test that for every possible value of x.
If all you want to do is be reasonably confident that (lambda (x) (+ x 1)) and (lambda (y) (+ y 1)) are the same, then you might try asserting that
(map (lambda (x) (+ x 1)) [(-5) (-4) (-3) (-2) (-1) 0 1 2 3 4 5])
and
(map (lambda (y) (+ y 1)) [(-5) (-4) (-3) (-2) (-1) 0 1 2 3 4 5])
are the same in your unit tests.

SICP 2.64 order of growth of recursive procedure

I am self-studyinig SICP and having a hard time finding order of growth of recursive functions.
The following procedure list->tree converts an ordered list to a balanced search tree:
(define (list->tree elements)
(car (partial-tree elements (length elements))))
(define (partial-tree elts n)
(if (= n 0)
(cons '() elts)
(let ((left-size (quotient (- n 1) 2)))
(let ((left-result (partial-tree elts left-size)))
(let ((left-tree (car left-result))
(non-left-elts (cdr left-result))
(right-size (- n (+ left-size 1))))
(let ((this-entry (car non-left-elts))
(right-result (partial-tree (cdr non-left-elts)
right-size)))
(let ((right-tree (car right-result))
(remaining-elts (cdr right-result)))
(cons (make-tree this-entry left-tree right-tree)
remaining-elts))))))))
I have been looking at the solution online, and the following website I believe offers the best solution but I have trouble making sense of it:
jots-jottings.blogspot.com/2011/12/sicp-exercise-264-constructing-balanced.html
My understanding is that the procedure 'partial-tree' repeatedly calls three argument each time it is called - 'this-entry', 'left-tree', and 'right-tree' respectively. (and 'remaining-elts' only when it is necessary - either in very first 'partial-tree' call or whenever 'non-left-elts' is called)
this-entry calls : car, cdr, and cdr(left-result)
left-entry calls : car, cdr, and itself with its length halved each step
right-entry calls: car, itself with cdr(cdr(left-result)) as argument and length halved
'left-entry' would have base 2 log(n) steps, and all three argument calls 'left-entry' separately.
so it would have Ternary-tree-like structure and the total number of steps I thought would be similar to 3^log(n). but the solution says it only uses each index 1..n only once. But doesn't 'this-entry' for example reduce same index at every node separate to 'right-entry'?
I am confused..
Further, in part (a) the solution website states:
"in the non-terminating case partial-tree first calculates the number
of elements that should go into the left sub-tree of a balanced binary
tree of size n, then invokes partial-tree with the elements and that
value which both produces such a sub-tree and the list of elements not
in that sub-tree. It then takes the head of the unused elements as the
value for the current node"
I believe the procedure does this-entry before left-tree. Why am I wrong?
This is my very first book on CS and I have yet to come across Master Theorem.
It is mentioned in some solutions but hopefully I should be able to do the question without using it.
Thank you for reading and I look forward to your kind reply,
Chris
You need to understand how let forms work. In
(let ((left-tree (car left-result))
(non-left-elts (cdr left-result))
left-tree does not "call" anything. It is created as a new lexical variable, and assigned the value of (car left-result). The parentheses around it are just for grouping together the elements describing one variable introduced by a let form: the variable's name and its value:
(let ( ( left-tree (car left-result) )
;; ^^ ^^
( non-left-elts (cdr left-result) )
;; ^^ ^^
Here's how to understand how the recursive procedure works: don't.
Just don't try to understand how it works; instead analyze what it does, assuming that it does (for the smaller cases) what it's supposed to do.
Here, (partial-tree elts n) receives two arguments: the list of elements (to be put into tree, presumably) and the list's length. It returns
(cons (make-tree this-entry left-tree right-tree)
remaining-elts)
a cons pair of a tree - the result of conversion, and the remaining elements, which are supposed to be none left, in the topmost call, if the length argument was correct.
Now that we know what it's supposed to do, we look inside it. And indeed assuming the above what it does makes total sense: halve the number of elements, process the list, get the tree and the remaining list back (non-empty now), and then process what's left.
The this-entry is not a tree - it is an element that is housed in a tree's node:
(let ((this-entry (car non-left-elts))
Setting
(right-size (- n (+ left-size 1))
means that n == right-size + 1 + left-size. That's 1 element that goes into the node itself, the this-entry element.
And since each element goes directly into its node, once, the total running time of this algorithm is linear in the number of elements in the input list, with logarithmic stack space use.

I am trying to recursively sum integers input by the user

I am brand spanking new to scheme. To the extent that tonight is the first time I've ever played around with it aside from what a powerpoint slide explained to me in class. I have to write a scheme program that takes a user input operator and then performs that operation on the numbers that follow it. (in other words implement my own version of schemes built-in '+', '*', etc operators). The catch is that I have to use recursion. Trying to maneuver my way around this scheme syntax is making it very difficult for me to even figure out where to start.
So, I decided to start with some code that at least recursively sums up values entered by the user (without worrying about recognizing the user inputting an operator and parentheses). I just plain can't figure out how to make it work. Here's what I'm trying:
(define run (lambda (x)
(cond
((eqv? x "a") (display x) )
(else (+ x (run(read))))
)))
(run (read))
The condition to check if x equals "a" was intended to be a way for me to break the recursion right now. In the final version, the input will be in between a set of parenthesis, but I thought I'd cross that bridge when I come to it. Anyways, the code just keeps accepting input, and never stops. So, the condition to display x is never getting called I guess. I know I'm probably doing everything wrong, and I would really appreciate some pointers.
EDIT: OK, I'm at least starting to realize that something isn't making sense. Such as my displaying x won't actually give the sum since I'm not storing the sum in x in any way. However, I still don't understand why the code isn't at least stopping when I enter the letter a.
EDIT 2: I've got it to work but only when the condition is equal to 1. How can I get it to recognize an input of a char or better yet a right parenthesis to get it to end the recursion just like it is with the 1? :
(define run (lambda (x)
(cond
((eq? x 1) 0)
(else (+ x (run(read))))
)))
(run (read))
End-Of-File (EOF) marks the end of input. Scheme has a procedure eof-object? for this. Your code is basically correct:
(define (run x)
(if (eof-object? x)
0
(+ x (run (read)))))
You end the run using Control-D. If for some reason you don't have access to eof-object? just use a special character like X. In that case your test would be (eq? x #\X)
(define (addition-loop)
(let ((answer (read)))
(if (number? answer)
(+ answer (addition-loop))
0)))
This will break as soon as you enter something that isn't a number.
In addition as you can see, you can have a function of zero arguments.
Anyways the problem with your code is when you enter a into read, it's value it actually the same a 'a or (quote a), rather than "a".
(eqv? 'a "a")
;Value: #f

Accumulators, conj and recursion

I've solved 45 problems from 4clojure.com and I noticed a recurring problem in the way I try to solve some problems using recursion and accumulators.
I'll try to explain the best I can what I'm doing to end up with fugly solutions hoping that some Clojurers would "get" what I'm not getting.
For example, problem 34 asks to write a function (without using range) taking two integers as arguments and creates a range (without using range). Simply put you do (... 1 7) and you get (1 2 3 4 5 6).
Now this question is not about solving this particular problem.
What if I want to solve this using recursion and an accumulator?
My thought process goes like this:
I need to write a function taking two arguments, I start with (fn [x y] )
I'll need to recurse and I'll need to keep track of a list, I'll use an accumulator, so I write a 2nd function inside the first one taking an additional argument:
(fn
[x y]
((fn g [x y acc] ...)
x
y
'())
(apparently I can't properly format that Clojure code on SO!?)
Here I'm already not sure I'm doing it correctly: the first function must take exactly two integer arguments (not my call) and I'm not sure: if I want to use an accumulator, can I use an accumulator without creating a nested function?
Then I want to conj, but I cannot do:
(conj 0 1)
so I do weird things to make sure I've got a sequence first and I end up with this:
(fn
[x y]
((fn g [x y acc] (if (= x y) y (conj (conj acc (g (inc x) y acc)) x)))
x
y
'()))
But then this produce this:
(1 (2 (3 4)))
Instead of this:
(1 2 3 4)
So I end up doing an additional flatten and it works but it is totally ugly.
I'm beginning to understand a few things and I'm even starting, in some cases, to "think" in a more clojuresque way but I've got a problem writing the solution.
For example here I decided:
to use an accumulator
to recurse by incrementing x until it reaches y
But I end up with the monstrosity above.
There are a lot of way to solve this problem and, once again, it's not what I'm after.
What I'm after is how, after I decided to cons/conj, use an accumulator, and recurse, I can end up with this (not written by me):
#(loop [i %1
acc nil]
(if (<= %2 i)
(reverse acc)
(recur (inc i) (cons i acc))))
Instead of this:
((fn
f
[x y]
(flatten
((fn
g
[x y acc]
(if (= x y) acc (conj (conj acc (g (inc x) y acc)) x)))
x
y
'())))
1
4)
I take it's a start to be able to solve a few problems but I'm a bit disappointed by the ugly solutions I tend to produce...
i think there are a couple of things to learn here.
first, a kind of general rule - recursive functions typically have a natural order, and adding an accumulator reverses that. you can see that because when a "normal" (without accumulator) recursive function runs, it does some work to calculate a value, then recurses to generate the tail of the list, finally ending with an empty list. in contrast, with an accumulator, you start with the empty list and add things to the front - it's growing in the other direction.
so typically, when you add an accumulator, you get a reversed order.
now often this doesn't matter. for example, if you're generating not a sequence but a value that is the repeated application of a commutative operator (like addition or multiplication). then you get the same answer either way.
but in your case, it is going to matter. you're going to get the list backwards:
(defn my-range-0 [lo hi] ; normal recursive solution
(if (= lo hi)
nil
(cons lo (my-range-0 (inc lo) hi))))
(deftest test-my-range-1
(is (= '(0 1 2) (my-range-0 0 3))))
(defn my-range-1 ; with an accumulator
([lo hi] (my-range-1 lo hi nil))
([lo hi acc]
(if (= lo hi)
acc
(recur (inc lo) hi (cons lo acc)))))
(deftest test-my-range-1
(is (= '(2 1 0) (my-range-1 0 3)))) ; oops! backwards!
and often the best you can do to fix this is just reverse that list at the end.
but here there's an alternative - we can actually do the work backwards. instead of incrementing the low limit you can decrement the high limit:
(defn my-range-2
([lo hi] (my-range-2 lo hi nil))
([lo hi acc]
(if (= lo hi)
acc
(let [hi (dec hi)]
(recur lo hi (cons hi acc))))))
(deftest test-my-range-2
(is (= '(0 1 2) (my-range-2 0 3)))) ; back to the original order
[note - there's another way of reversing things below; i didn't structure my argument very well]
second, as you can see in my-range-1 and my-range-2, a nice way of writing a function with an accumulator is as a function with two different sets of arguments. that gives you a very clean (imho) implementation without the need for nested functions.
also you have some more general questions about sequences, conj and the like. here clojure is kind-of messy, but also useful. above i've been giving a very traditional view with cons based lists. but clojure encourages you to use other sequences. and unlike cons lists, vectors grow to the right, not the left. so another way to reverse that result is to use a vector:
(defn my-range-3 ; this looks like my-range-1
([lo hi] (my-range-3 lo hi []))
([lo hi acc]
(if (= lo hi)
acc
(recur (inc lo) hi (conj acc lo)))))
(deftest test-my-range-3 ; except that it works right!
(is (= [0 1 2] (my-range-3 0 3))))
here conj is adding to the right. i didn't use conj in my-range-1, so here it is re-written to be clearer:
(defn my-range-4 ; my-range-1 written using conj instead of cons
  ([lo hi] (my-range-4 lo hi nil))
  ([lo hi acc]
    (if (= lo hi)
      acc
      (recur (inc lo) hi (conj acc lo)))))
(deftest test-my-range-4
(is (= '(2 1 0) (my-range-4 0 3))))
note that this code looks very similar to my-range-3 but the result is backwards because we're starting with an empty list, not an empty vector. in both cases, conj adds the new element in the "natural" position. for a vector that's to the right, but for a list it's to the left.
and it just occurred to me that you may not really understand what a list is. basically a cons creates a box containing two things (its arguments). the first is the contents and the second is the rest of the list. so the list (1 2 3) is basically (cons 1 (cons 2 (cons 3 nil))). in contrast, the vector [1 2 3] works more like an array (although i think it's implemented using a tree).
so conj is a bit confusing because the way it works depends on the first argument. for a list, it calls cons and so adds things to the left. but for a vector it extends the array(-like thing) to the right. also, note that conj takes an existing sequence as first arg, and thing to add as second, while cons is the reverse (thing to add comes first).
all the above code available at https://github.com/andrewcooke/clojure-lab
update: i rewrote the tests so that the expected result is a quoted list in the cases where the code generates a list. = will compare lists and vectors and return true if the content is the same, but making it explicit shows more clearly what you're actually getting in each case. note that '(0 1 2) with a ' in front is just like (list 0 1 2) - the ' stops the list from being evaluated (without it, 0 would be treated as a command).
After reading all that, I'm still not sure why you'd need an accumulator.
((fn r [a b]
(if (<= a b)
(cons a (r (inc a) b))))
2 4)
=> (2 3 4)
seems like a pretty intuitive recursive solution. the only thing I'd change in "real" code is to use lazy-seq so that you won't run out of stack for large ranges.
how I got to that solution:
When you're thinking of using recursion, I find it helps to try and state the problem with the fewest possible terms you can think up, and try to hand off as much "work" to the recursion itself.
In particular, if you suspect you can drop one or more arguments/variables, that is usually the way to go - at least if you want the code to be easy to understand and debug; sometimes you end up compromising simplicity in favor of execution speed or reducing memory usage.
In this case, what I thought when I started writing was: "the first argument to the function is also the start element of the range, and the last argument is the last element". Recursive thinking is something you kind of have to train yourself to do, but a fairly obvious solution then is to say: a range [a, b] is a sequence starting with element a followed by a range of [a + 1, b]. So ranges can indeed be described recursively. The code I wrote is pretty much a direct implementation of that idea.
addendum:
I've found that when writing functional code, accumulators (and indexes) are best avoided. Some problems require them, but if you can find a way to get rid of them, you're usually better off if you do.
addendum 2:
Regarding recursive functions and lists/sequences, the most useful way to think when writing that kind of code is to state your problem in terms of "the first item (head) of a list" and "the rest of the list (tail)".
I cannot add to the already good answers you have received, but I will answer in general. As you go through the Clojure learning process, you may find that many but not all solutions can be solved using Clojure built-ins, like map and also thinking of problems in terms of sequences. This doesn't mean you should not solve things recursively, but you will hear -- and I believe it to be wise advice -- that Clojure recursion is for solving very low level problems you cannot solve another way.
I happen to do a lot of .csv file processing, and recently received a comment that nth creates dependencies. It does, and use of maps can allow me to get at elements for comparison by name and not position.
I'm not going to throw out the code that uses nth with clojure-csv parsed data in two small applications already in production. But I'm going to think about things in a more sequency way the next time.
It is difficult to learn from books that talk about vectors and nth, loop .. recur and so on, and then realize learning Clojure grows you forward from there.
One of the things I have found that is good about learning Clojure, is the community is respectful and helpful. After all, they're helping someone whose first learning language was Fortran IV on a CDC Cyber with punch cards, and whose first commercial programming language was PL/I.
If I solved this using an accumulator I would do something like:
user=> (defn my-range [lb up c]
(if (= lb up)
c
(recur (inc lb) up (conj c lb))))
#'user/my-range
then call it with
#(my-range % %2 [])
Of course, I'd use letfn or something to get around not having defn available.
So yes, you do need an inner function to use the accumulator approach.
My thought process is that once I'm done the answer I want to return will be in the accumulator. (That contrasts with your solution, where you do a lot of work on finding the ending-condition.) So I look for my ending-condition and if I've reached it, I return the accumulator. Otherwise I tack on the next item to the accumulator and recur for a smaller case. So there are only 2 things to figure out, what the end-condition is, and what I want to put in the accumulator.
Using a vector helps a lot because conj will append to it and there's no need to use reverse.
I'm on 4clojure too, btw. I've been busy so I've fallen behind lately.
It looks like your question is more about "how to learn" then a technical/code problem. You end up writing that kind of code because from whatever way or source you learned programming in general or Clojure in specific has created a "neural highway" in your brain that makes you thinking about the solutions in this particular way and you end up writing code like this. Basically whenever you face any problem (in this particular case recursion and/or accumulation) you end up using that "neural highway" and always come up with that kind of code .
The solution for getting rid of this "neural highway" is to stop writing code for the moment, keep that keyboard away and start reading a lot of existing clojure code (from existing solutions of 4clojure problem to open source projects on github) and think about it deeply (even read a function 2-3 times to really let it settle down in your brain). This way you would end up destroying your existing "neural highway" (which produce the code that you write now) and will create a new "neural highway" that would produce the beautiful and idiomatic Clojure code. Also, try not to jump to typing code as soon as you saw a problem, rather give yourself some time to think clearly and deeply about the problem and solutions.

Resources