- Source code: https://github.com/nklein/alternatives
When someone is writing code, there is often more than one way they could accomplish a given task. After they have explored a number of options, they usually remove all but the final version from the code. If they were being extraordinarily careful, there might be separate commits in the version control system which contained the alternatives. If they were being extraordinarily forward-looking, they may have left a comment in the code about what alternatives they tried and why they settled on the one that they did.
Even if they did both of those things, there might be reasons you would want to try out the alternatives again to see if the original assumptions still hold. You could futz around with version control and try to bring the old versions of that function into the current code base. Or, you could try to rewrite and re-debug the alternatives yourself. But, wouldn’t it be nice if the alternatives were still all at your fingertips, at least while the code is still in active development?
The ALTERNATIVES
macro provides one way of accomplishing this. One can specify tagged alternatives. The macro will expand into the body of one of the alternative clauses. Each alternative clause is of the form (TAG &BODY BODY)
where the TAG
is either a SYMBOL
or STRING
.
If any clause is preceded by a symbol or string ***
, the macro expands to the BODY
of the first such clause. Otherwise, if the TAG
of any clause is the symbol or string ***
, FINAL
, or BLESSED
, then the macro expands to the body of the first such clause. Otherwise, the macro expands to the body of the last alternative clause.
In the following example, the macro expands into the body of the final alternative clause:
(alternatives
(i-wanted-to-do-this
(loop :for i :to n :summing (* i i)))
(my-first-attempt-was-something-like-this
(do ((i 0 (1+ i))
(sum 0 (+ sum (* i i))))
((> i n) sum)))
(but-i-could-not-do-that-because
"Some people find a do-loop too hard to read.")
(now-i-know-better-and-can-do-this
(/ (* n (1+ n) (1+ (+ n n)) 6)))))
If we wanted to quickly try out the MY-FIRST-ATTEMPT-...
clause, we could precede it with a ***
:
(alternatives
(i-wanted-to-do-this
(loop :for i :to n :summing (* i i)))
***
(my-first-attempt-was-something-like-this
(do ((i 0 (1+ i))
(sum 0 (+ sum (* i i))))
((> i n) sum)))
(but-i-could-not-do-that-because
"Some people find a do-loop too hard to read.")
(now-i-know-better-and-can-do-this
(/ (* n (1+ n) (1+ (+ n n)) 6)))))
If one uses ALTERNATIVES*
and provides a name for the alternatives, then one can check the #'DOCUMENTATION
for the alternative at a later time to see which alternative was last macroexpanded.
(alternatives* sum-of-squares-implementations
(i-wanted-to-do-this
(loop :for i :to n :summing (* i i)))
***
(my-first-attempt-was-something-like-this
"Using a DO-loop"
(do ((i 0 (1+ i))
(sum 0 (+ sum (* i i))))
((> i n) sum)))
(but-i-could-not-do-that-because
"Some people find a do-loop too hard to read.")
(now-i-know-better-and-can-do-this
(/ (* n (1+ n) (1+ (+ n n)) 6)))))
(documentation 'sum-of-squares-implementations
'alt:alternatives)
=> "MY-FIRST-ATTEMPT-WAS-SOMETHING-LIKE-THIS
Using a DO-loop"