0

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?

Steven Doggart
  • 43,358
  • 8
  • 68
  • 105
Aadit M Shah
  • 72,912
  • 30
  • 168
  • 299
  • 2
    I'm not sure whether or not this question should have the [tag:common-lisp] tag. While the language supports dynamic scoping, the default is lexical, so you typically don't run into problems with dynamic scoping. [tag:elisp], I think, has also changed its default behavior in recent times. That said, where you don't have free variables you won't run into problems. If a language doesn't have lexical scoping, it doesn't make sense to talk about lexical closures in it. (These comments don't really address what _to_ do, though.) – Joshua Taylor Oct 04 '13 at 18:02
  • 2
    I think this question might be off-topic, since there's no clear right or wrong answer, but there are some similar questions on Stack Overflow, including [How to live with Emacs Lisp dynamic scoping?](http://stackoverflow.com/q/3786033/1281433). The highest voted answer on that question begins with "**It isn't that bad.** The main problems can appear with 'free variables' in functions." which is very much like @TreyJackson's answer here. [This answer](http://stackoverflow.com/a/3791877/1281433) specially speaks to working around issues with higher order functions (your main problem here). – Joshua Taylor Oct 04 '13 at 18:17
  • 2
    Joshua Taylor: FYI Emacs hasn't changed its default behaviour. Emacs 24 *supports* lexical scope, but dynamic binding is (and will hopefully remain) the default. – phils Oct 05 '13 at 04:00
  • +1 to @phils hope that dynamic binding will remain the default. The right model is that provided by ***Common Lisp***. Emacs Lisp is close to that now, but not quite there yet. – Drew Oct 26 '13 at 17:19

2 Answers2

4

I would keep in mind that these languages don't have closures.

Maybe they have, but with an extra construct (like some Lisp languages a few decades ago). Even worse, sometimes the Interpreter and the Compiler had different semantics - like in some old Lisp dialects decades ago.

There is a reason Lisp mostly switched to lexical binding (Scheme explored it in the mid 70s, Common Lisp got it the mid 80s, Emacs Lisp just recently got support for it.).

Basically if you want to do advanced functional programming, stay away from dynamically scoped languages.

Use SML, Scheme, CL, Haskell, Racket, OCAML, ... instead.

Rainer Joswig
  • 136,269
  • 10
  • 221
  • 346
  • If you really want to do ***functional*** programming then *stay away from Lisp*, including Scheme and Common Lisp. Unless you limit yourself to a pure subset of a Lisp implementation, programming in Lisp is *not* "purely functional" programming. If you want to program in ***Lisp***, on the other hand, Common Lisp is your friend. – Drew Oct 26 '13 at 17:24
3

Minimize the use of free variables.

Trey Jackson
  • 73,529
  • 11
  • 197
  • 229
  • 1
    Could you please elaborate? That's just the start, there's more to it: document the use of free variables in functions; declare dynamic variables at top-level; document what and when such variable can change and when it makes sense to use it; what restrictions it has (e.g. possible values, type, state, interfaces); how you can use it (e.g. can it be modified? can its value be captured and used outside the dynamic extent?); etc. There's also the need to know what the language or implementation supports, e.g. is dynamic binding per-thread or a process-wide temporary change? – acelent Oct 08 '13 at 13:04
  • @PauloMadeira Sure, there's an art/science on how to build a system in a dynamically scoped language. This question appeared to be more of the "what to do when these functions fail to work as expected" – Trey Jackson Oct 08 '13 at 16:44