I recently started writing non-trivial programs in Logo (non-trivial in the sense no turtle graphics). One of the major hurdles I ran into was dynamic scoping. For example consider the following program:
to foldl :f :acc :list [:index 1]
output ifelse empty? :list [:acc] [
(foldl :f (invoke :f :acc first :list :index) butfirst :list :index + 1)
]
end
to permute :list
output ifelse empty? :list [[[]]] [
foldl [[permutations item index]
sentence :permutations map [[list]
fput :item :list
] permute delete :index :list
] [] :list
]
end
The permute
function works for the empty list []
for which it produces the output [[]]
and lists with a single item [a
] for which it produces the output [[a]]
. However it fails for lists with two or more elements.
Guess why it fails? The lambda function passed to foldl
from permute
accesses the free variable list
and because foldl
also has a local variable named list
it accesses the wrong variable. Because foldl
is defined recursively the list
variable keeps shrinking with each iteration.
I solved this problem by saving a copy of the original list in the foldl
function as follows:
to foldl :f :acc :list [:index 1] [:original :list]
output ifelse empty? :list [:acc] [
(foldl :f (invoke :f :acc first :list :index :original)
butfirst :list :index + 1 :original)
]
end
to permute :list
output ifelse empty? :list [[[]]] [
foldl [[permutations item index list]
sentence :permutations map [[list]
fput :item :list
] permute delete :index :list
] [] :list
]
end
However it took me the better part of the evening to figure out what was causing this strange error. I had never programmed in a language with dynamic scoping before (save small snippets of bash scripting).
Hence my question is as follows: what should you keep in mind when writing functions in languages which have dynamic scoping? What are the best practices? How do you avoid common pitfalls?
Minimize the use of free variables.