4 ms·
I think that this is a good example of where dynamic scope is helpful. We’re used to lexical scope: it’s easy to reason about, and it is a really good default.
by rauhl 7y ago
I think that this is a good example of where dynamic scope is helpful.
We’re used to lexical scope: it’s easy to reason about, and it is a really good default. But sometimes it makes sense for one function to apply settings for all the functions it calls, without interfering with other functions, scopes, threads or processes (like setting a global would).
It’d be nice to be able to say ‘this function should timeout within 10 ms’ and then any function called will just automatically timeout.
Go’s contexts integrate timeouts and cancellation, and permit one to add any value, should one wish to, but you have to be disciplined and add a context argument to every single function. It’d be better, I think, to support it natively in the language. Lisp does this: any variable declared with DEFPARAMETER or DEFVAR is dynamic, and you can locally declare something dynamic too.
One can fake dynamic scoping with thread-local storage and stacks or linked lists, if one needs it, but it can get ugly.
Dynamic scoping doesn’t get the attention or respect I think it deserves. It’s arguably the wrong thing by default, but when it’s useful, it’s really useful.
- lmm 7y agoHave you looked at Scala implicits?
- The_rationalist 7y agoThis should be the accepted answer.
- quietbritishjim 7y agoI think that what you're describing is how Trio's cancellation scopes work [1]. Trio is an alternative async library for Python [2] (as opposed to asyncio). It's pretty neat. [1] https://vorpus.org/blog/timeouts-and-cancellation-for-humans/ https://vorpus.org/blog/timeouts-and-cancellation-for-humans... [2] https://github.com/python-trio/trio https://github.com/python-trio/trio [2]
- BiteCode_dev 7y agoIt's possible to implement the concept in asyncio as well, just not enforce it for dependancies due to ensure_future(). See ayo for a very rougth draft of the idea: https://github.com/Tygs/ayo https://github.com/Tygs/ayo
- 7373737373 7y ago>It’d be nice to be able to say ‘this function should timeout within 10 ms’ and then any function called will just automatically timeout. Stackless Python has this ability - it can even resume suspended functions: https://stackless.readthedocs.io/en/latest/library/stackless/pickling.html https://stackless.readthedocs.io/en/latest/library/stackless... Additionally, suspended tasklets can be serialized, then saved to a file or sent over the network and resumed elsewhere!
- zbobet2012 7y agoI'm glad I'm not the only one who noticed a context is basically a form of dynamic scoping, but with a tendency towards weaker type guarantees. Dynamic scope is incredibly useful for cross-cutting concerns. However, I implemented this but found several problems in practice I've never reconciled. The two big issues where: 1) "User managed" namespaces 2) Multi-process programs, or perhaps multi scope programs The issue with "user-managed" namespaces is basically one of lexical scoping. In traditional dynamic scope, all variables in the dynamic scope are global. As such people tend to accidentally overwrite other people's variables. I've thought through some ways around this, but none have been elegant. The more important, and foundational one takes some explaining. Consider some code: type Work struct { Ctx context.Context Result chan<- int } func NewWork(ctx context.Context, c chan<- int) *Work { return &Work{ Ctx: ctx, Result: c, } } type Worker struct { c <-chan *Work } func NewWorker(c <-chan *Work) { return &Worker{ c: c, } } func (w *Worker) Work(ctx context.Context) { for work := range w.c { // We now have two contexts, // the context of the work call, and // our "local" context" // Both are usefull! } } func main() { workers := make([]*Worker, 0, 10) workChan := make(chan *Work, len(workers)) for i, _ := range workers { workerContext := context.Default() workers[i] = NewWorker(workChan) go w.Work(workerContext) } http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) { httpContext := context.Default() work := NewWork(ctx, result) fmt.Fprintf(w, "Got Result, %q", <-result) }) log.Fatal(http.ListenAndServe(":8080", nil)) } As we can see in the work function above, context is more than just your function chain. Now arguably dynamic scope doesn't need to handle this, but it is very commonly the case in CSP/etc. based languages our most relevant scope does not come from dynamic scope.
- uhoh-itsmaciek 7y agoFront-end code is a different beast, but this immediately reminded me of React's Context API: is you squint, it sort of looks like dynamic scope. It's also nestable, and since you can wrap the context provider in a custom component, you can have it consume the parent context (if any) and compose them however you like. Plus since accessing that context is explicit, you avoid the issue of dynamic scope being harder to reason about in program execution. I wonder if there's a reasonable way to apply this approach to more general-purpose code.