Overall
The code achieves the most important goal. It runs and produces the correct answer. Using helper/auxiliary methods is a very useful practice and makes the code clearer.
Naming
As mentioned in the accepted answer, it is standard practice to use lower case for all names. The reason is that historically, the Lisp pretty printer prints in ALLCAPS
. Using lower case in the source code allows a running program to determine if a source file or other input is machine generated while running...and sometimes Lisp programmers want to do that.
Because Fibonacci is in the realm of mathematics, it might be better to use more mathematical names such as i
and j
rather than a
and b
.
Compound names in Lisp are traditionally formmated using dashes. For example, foo-bar
rather than "FooBar", "fooBar" or foo_bar. The -
in foo-bar
cannot be mistaken for the arithmetic subtraction operator because operators always come first in Lisp's s-expressions.
Structure
It is not uncommon for Common-Lisp programs to iterate over loops. On the other hand, another common idiom in Common-Lisp is recursion. Because it is common for contemporary Common-Lisp implementations to optimize tail-calls, it is possible to express loops recursively without danger of running out of stack space.
Lexical Scope
In Common-Lisp new functions can be introduced within the lexical scope of another function using labels
and flet
. The difference is that labels
allows recursive calls and flet
does not. Given that most computing devices provide vast resources relative to historical platforms, it might make sense to default to labels
and save flet
for highly constrained platforms.
Illustrative example
(defun euler-2 (max)
(labels
((next (n-2 n-1 sum)
(setq n (+ n-2 n-1))
(cond
((>= n max) sum)
((evenp n) (next n-1 n (+ n sum)))
(t (next n-1 n sum)))))
(next 1 1 0)))
- The function
euler-2
is named for it's problem domain. It is written to take advantage of SBCL's tail call optimization.
- It defines an lexically scoped procedure,
next
internally.
- In the context of
next
, the current value is n
and it's predecessors are "n minus two" and "n minus one".
(next 1 1 0)
kicks off next
's recursive procedure with initial values.
Circling Back
The original program could be refactored to incorporate several of the suggestions while maintaining the its original iterative approach.
(defun fibo-nums (max)
"list of fibonacci numbers that are below maxNum"
(labels
((sum (l)
"sum a list"
(reduce #'+ l))
(filter (l predicate)
"filters a list"
(remove-if (complement predicate) l))
;; add an inner function that does
;; all the work
(f ()
(progn
(setq a 1)
(setq b 2)
(setq next (+ a b))
(loop while (< b max) do
(setq next (+ a b))
(setq a b)
(setq b next)
collecting a))))
;; call the inner function
(sum (filter (f) #'evenp))))
Note that the locally scoped function f
takes no arguments and uses progn
to create an executable block of code.