So, here we go with a simple Common Lisp attempt. The Lost in scope
article begins with defining a very simple function returning a boolean
value, only true when it’s not
Monday is special
Keep in mind that the following example has been choosen to be simple yet
offer a case of lexical binding shadowing. It looks convoluted. Focus on
(defparameter *days* '(monday tuesday wednesday thursday friday saturday sunday) "List of days in the week") (defun any-day-but-monday? (day) "Returns a generalized boolean, true unless DAY is 'monday" (member day (remove-if (lambda (day) (eq day 'monday)) *days*)))
So as you can see, in Common Lisp we just get away with a list of symbols rather than a string that we split to have a list of strings, or an array of strings, as in the examples with python and ruby.
Now, the generalized boolean is either
nil to mean false, or anything
else to mean
true, and in that example the return value
is a sub-list that begins where the member was found:
CL-USER> (any-day-but-monday? 'monday) NIL CL-USER> (any-day-but-monday? 'tuesday) (TUESDAY WEDNESDAY THURSDAY FRIDAY SATURDAY SUNDAY)
Oh, and as we work with Common Lisp, we’re having a
to play directly with our code, no need to add interactive stanzas in the
main program text file just to be able to play with it.
In Emacs Slime we just use
on a form to have it available in the REPL, or
C-c C-l to load the
whole file we’re working on.
So, we see that Common Lisp scoping rules are silently doing the right thing here. Within the remove-if call we define a lambda function taking a single parameter called day. It so happens that this parameter is shadowing the any-day-but-monday? function parameter, and that shadowing only happens in the lexical scope of the lambda we are creating. For a detailed discussion about that concept, I would refer you to the Scope and Extent chapter of Common Lisp the Language, 2nd Edition.
In Common Lisp we have both lexical scope and dynamic extents, and a variable defined with defparameter or defvar or that you otherwise declare special will have a dynamic extent. Hence this section title.
Now, the lost in scope article tries some more at finding a solution around the scoping rules of the python and ruby languages, where the developer can not easily instruct the language about the scoping rules he wants to be using in a case by case way, as far as I can see.
First, let’s reproduce the problem by using a single variable that we bind in all the closures. Those are called callbacks in the original article, so I’ve kept using that name here.
(defparameter *callbacks-all-sunday* (loop :for day :in *days* :collect (lambda () day)) "loop binds DAY only once")
In that example, there’s only a single variable day that we reuse throughout
the loop construct, so that when the loop ends, we have a list of closures
all refering to the same variable, and this variable, by the end of the
sunday as its value.
CL-USER> (mapcar #'funcall *callbacks-all-sunday*) (SUNDAY SUNDAY SUNDAY SUNDAY SUNDAY SUNDAY SUNDAY)
Closures, take 2
Now, the way to have what we want here, that is a list of closures each having its own variable.
(defparameter *callbacks* (mapcar (lambda (day) ;; for each day, produce a separate closure ;; around its own lexical variable day (lambda () day)) *days*) "A list of callbacks to return the current day...")
And there we go:
CL-USER> (mapcar #'funcall *callbacks*) (MONDAY TUESDAY WEDNESDAY THURSDAY FRIDAY SATURDAY SUNDAY)
Scoping rules are very important in any programming language, functional or not, and must be well understood by programmers. I find that once again, that topic has received a very deep thinking in Common Lisp, and the language is giving all the options to its developers.
I want to stress that in Common Lisp the scope rules are very clearly defined in the standard documentation of the language. For instance, defun and let both introduce a lexical binding, defvar and defparameter introduce a dynamic variable.
Also, as a user of the language you have the ability to declare any
variable as being special in order to introduce yourself a dynamic
C you can declare some variables as being static, which is
something else and frown with a very different set of problems.