"Macros compose with modules to preserve lexical scoping. Delimited continuations can transfer control through functional combinators while preserving referential transparency. Dynamic bindings behave sanely when continuations are unwound and reinstated. Scheme is a language that hangs together: its features are carefully chosen to produce sound semantics when used in combination."
Can somebody with more knowledge elaborate on each of those first three sentences? Or point me at papers to read?
I think the point about referential transparency is that when you call a delimited continuation, unlike an undelimited continuation, it will actually return. Delimited continuations can be pure functions, while undelimited continuations always have the side effect of ditching the calling code.
I think people have implemented delimited continuations in terms of undelimited continuations, but if we consider delimited continuations to be innate to a language, that probably has static analysis advantages.
---
"Macros compose with modules to preserve lexical scoping."
I don't think that's even true. XD My experience with macros and modules is that, in order to preserve lexical scoping, I frequently have to think about the ramifications of macros on modules and vice versa. To me, they're not features that "compose," they're features that are really tightly coupled.
---
"Dynamic bindings behave sanely when continuations are unwound and reinstated."
IMO, that's just what it means to be a dynamic binding. Without continuations, you don't need dynamic bindings, because their "temporarily set" behavior can be implemented in terms of regular old mutable boxes.
With continuations, I think it helps to see the program in the shape of a stack: Usually the program merely grows and shrinks, but when there are (undelimited) continuations in the mix, there are multiple stacks branching from each other that can grow and shrink independently, with only one being active at any given time. A dynamic binding is a special box that lets you give it a value only seen by the part of the program that emerges from the tip of this branch.
With a mutable box, you can remember the old value, insert the new value, run some code, and restore the old value. But at least in Arc, inserting the new value will happen "on the heap" rather than in a way that's local to the current continuation. Other continuations will observe that change, and often that isn't what we want.
Now, if we have 'dynamic-wind, we don't really need dynamic bindings again, 'cause we can maintain the value of a mutable box as we leave one program branch and enter another. But Racket's parameters still have an edge over this technique, because the body of 'parameterize is in tail position.
On another note, continuations aren't the only way we might consider the program's dynamic behavior to branch. So are threads. But thread-local boxes are usually called just that.
Sorry for leaving you hanging. XD I've been here the whole time, but I've been a bit more of a silent lurker than usual. I tend to check Arc Forum on my phone, at times when I'm already a bit tired and not patient enough to search out all the exact quotes and links I'd want to use in a reply. Thumb-typing is not my obstacle; dealing with poor interfaces for tabbed browsing is. :-p
---
"I don't follow the final two paragraphs, but I will reflect on them and read more about dynamic-wind."
I'll elaborate--er, rant about those paragraphs anyway. >.>
---
Me: "Now, if we have 'dynamic-wind, we don't really need dynamic bindings again, 'cause we can maintain the value of a mutable box as we leave one program branch and enter another. But Racket's parameters still have an edge over this technique, because the body of 'parameterize is in tail position."
In Java (or your favorite Java-like language :-p ), when an exception is thrown, the program races to unwind the stack, making a stop at each "catch" or "finally" to see if there's something else it needs to take care of along the way, and to see if it should stop unwinding.
In Scheme, when you call a continuation, the program unwinds itself down one stack branch and winds back up onto another, making a stop at each "dynamic-wind" to do the same kinds of duties. A dynamic-wind form has a before part and an after part. (It might as well be two separate forms.) Arc exposes the after part by way of 'protect and 'after, but it leaves out the before part.
The continuation-tree imagery is particularly important with 'dynamic-wind. During a continuation jump, we don't just do every after parts in the stack, swap in the new stack, and do all the before parts. We only wind back just as far as the point where the continuations meet. (But so many languages with continuations neglect to even provide 'dynamic-wind (cough Arc, Ruby cough) that I wouldn't be surprised to see Scheme implementations that do provide it but accidentally give it nonstandard semantics.)
---
Me: "On another note, continuations aren't the only way we might consider the program's dynamic behavior to branch. So are threads. But thread-local boxes are usually called just that."
As far as thread-local boxes go, I like to think of a multithreaded program as a tree where the initial thread is the root, and each thread it spawns is a branch coming off of it. In different branches of the tree I might want different configuration settings to hold, in a similar way to what I'd achieve with dynamic boxes. In Java this can be achieved with InheritableThreadLocal, and in Racket it can be achieved by constructing a thread cell with a truthy value for its "preserved" parameter.
I don't use threads much. The people who do apparently prefer to manage thread-local state on an individual thread-by-thread basis, rather than putting them in a scope tree like this. In Java, ThreadLocal is easier to type than InheritableThreadLocal, and in Racket, (make-thread-cell null 'preserved) is easier to type than (make-thread-cell null). Maybe that makes sense: A thread isn't always merely a "part" of its creator's computation the way a stack frame is; threads can be passed around and collected into all kinds of spaghetti-like dependency combinations. Maybe that's what people tend to do with them. :-p
The body of a dynamic-wind isn't in tail position, because just after it determines a return value (or other exit), it runs some extra code (the after part).
The Racket documentation specifically says that the last expression in a 'parameterize form is in tail position, so they must use some magic or other. What I suspect is that for every stack frame, Racket keeps a table of the parameterizations made by that stack frame. Any given parameterize form doesn't need to introduce a new stack frame; it can just mutate the most recent stack frame's association data structure, overwriting any previous value it had been binding to that parameter. Technically it's storing something on the stack this way, but it's only storing a maximum of one thing per parameter per frame.
In fact, they vaguely spell this out if you look in the right place in the Racket docs (http://docs.racket-lang.org/reference/eval-model.html#(part....): "Each frame is conceptually annotated with a set of continuation marks . A mark consists of a key and its value[...] For example, marks can be used [...] to implement dynamic scope."
John Shutt: "the word 'hygiene' had always put me in mind of sterile, sometimes-impractical labs, with perhaps overtones of compulsive hand-washing, so I didn't take it as a slur on the alternative; the alternative would be getting one's hands dirty, metaphor for not being impractically squeamish."http://lambda-the-ultimate.org/node/4345#comment-66882