6 ms·
This isn't dynamic scoping; this is just a global variable with a stack of values. I appreciate the syntax is sort of the same (though the * makes it different
by saurik 6y ago
This isn't dynamic scoping; this is just a global variable with a stack of values. I appreciate the syntax is sort of the same (though the * makes it different in a very important way) but the meaning isn't. You should at least implement this with thread local storage, though, if you are going to do this.
- jolmg 6y agoI think best practice among languages that support dynamic scoping is to only make use of it for global variables. As I understand it, one should only read or shadow, not modify, these variables. Since that's the case, besides the thread issue you mentioned, I'm not sure this solution is lacking. I don't know much C++, though, so I might be missing something.
- catern 6y agoIf you have multiple threads which try to bind the dynamically scoped variable to a new value, that should work fine and result in different values for the variable in each thread. In this implementation, the threads will corrupt the data structure and result in undefined behavior.
- jolmg 6y agoYes, that's why: > besides the thread issue you mentioned
- masklinn 6y ago> If you have multiple threads which try to bind the dynamically scoped variable to a new value, that should work fine and result in different values for the variable in each thread. That would work with proper threadlocals. However with dynamic variables you'd also get this for different sub-thread stacks. Not all languages can expose that but languages with generators do e.g. in Python if you `yield` then the stack is suspended, this means a threadlocal you've changed would not be rolled back, but a dynamic variable (which Python does not have) would.
- AnimalMuppet 6y agoOK, help me out here: If I'm only using it for global variables, what does the dynamic scoping do for me? Why not just use a normal global variable?
- jolmg 6y agoThe problem with local variables is that you can then not call a function that uses them unless you define such variables. One shouldn't be forced to know of such variables before calling such a function, since then they'd hardly be different from regular arguments. The difference with statically-scoped global variables is that you can't set them to different things in different threads, and you have to be careful to set it back to it's original value after you called the code you wanted. In other words, it's the same difference as using environment variables vs configuration files for executables.
- AnimalMuppet 6y agoIf I ever saw anyone I worked with trying to do something like "setting a global variable to different things in different threads", I would have very strong words for them. If they didn't listen, I'd have even stronger words for their manager: "Fire this bozo immediately". In what sane world is that considered a good idea? Is there any way in which doing so via "dynamic scoping" makes it any less of a horrible idea? I'm having trouble making sense of your first paragraph. Part of the point of local variables is that they're local. It doesn't matter whether the function you're calling uses them or not. If it uses something with the same name, it's their own copy, and you don't care whether they do or don't. If they want yours, you pass it in. What am I missing?
- jolmg 6y ago> If I ever saw anyone I worked with trying to do something like "setting a global variable to different things in different threads", I would have very strong words for them. Because global variables aren't used the same between the two types of languages. In lexically-scoped languages, it's bad practice overall to use global variables for anything except constants. In dynamically-scoped languages, they're used as an environment just like how environment variables are used among processes. You asked why not use "normal" (statically-scoped) global variables, and I replied on the difference. That doesn't mean that I support using them like that in such languages. Imagine environment variables didn't exist. A `su` command that modified the home directory in /etc/passwd for the duration of its subprocess to change it back when it dies would also seem pretty ugly for me. Indeed, if a language lacks dynamic scoping or environment variables didn't exist, the proper practice would be to pass the whole environment explicitly as arguments. That's what's typically done in statically-scoped languages, but it has the caveats I mentioned in this other comment: https://news.ycombinator.com/item?id=24545180 https://news.ycombinator.com/item?id=24545180 > I'm having trouble making sense of your first paragraph. Here's an example using Elisp: ; Turning off static-scoping (setq lexical-binding nil) ;; Bad practice (defun foo () bar) (foo) ;=> Debugger entered--Lisp error: (void-variable bar) (let ((bar 3)) (foo)) ;=> 3 ;; Good practice (defvar bar 2) (foo) ;=> 2 (let ((bar 3)) (foo)) ;=> 3
- kazinator 6y agoThread awareness is not a required part of the description of dynamic scoping. Thread local storage does not make it absolutely re-entrant. We could move the True Scotsman goalposts even farther out and say that we appreciate that the syntax being fine, but your approach doesn't work with interrupt handlers.
- saurik 6y agoThe syntax sort of looks the same but it isn't the same or "fine" if what you want is "dynamic scoping" (as dynamic scoping clearly is a way to scope things, not a way to set things). Is "thread local storage" the same as "state threading"? No. Does it look vaguely similar? Sure. Is "function argument binding" the same as "currying"? No. Could you imagine the former providing many of the benefits of the latter, or being how you might implement it? I guess? I mentioned thread-local storage just because if you are going to develop this you should take that into consideration, as that's a common thing that will burn a lot of people; it was an unrelated code quality point for something you should do if you are going to do this kind of global variable stack thing. You could though use it to build something that was actually scoped by having a generic global dictionary and then keeping the names inside of the functions; at least then you are providing the core base noun of "dynamic scoping". (And of course, as someone who has spent all of their time programming in C++ coroutines for over a year now, I am well aware that the thread local storage isn't sufficient to make this trick work correctly in every case.)
- munificent 6y ago> This isn't dynamic scoping; this is just a global variable with a stack of values. This isn't a latte; this is just an espresso with steamed milk added to it. :) What you describe basically is how dynamic scoping is mechanically implemented under the hood.
- masklinn 6y ago> What you describe basically is how dynamic scoping is mechanically implemented under the hood. It's certainly how that's commonly emulated but that can leak out e.g. CPython uses threadlocals for decimal contexts, but if you set a localcontext in a coroutine / generator and suspend that, the information leaks out. I assume the same happens with gevent unless you `patch_thread()`, and even then that assumes `decimal` always deref's threadlocals from the python-level module rather than statically resolve them.
- dapids 6y agoDescription !== Implementation
- brandmeyer 6y agoIMO, the most comprehensive solution to this mechanism is provided in Racket scheme's parameterize system. Racket's parameters are about as safe as global variables can get. https://docs.racket-lang.org/guide/parameterize.html https://docs.racket-lang.org/guide/parameterize.html What value does a forked thread get? The value at the dynamic scope of the parent at the point of thread creation. What happens if a delimited continuation is invoked by a different thread compared to the one that created the continuation? If a parameterize call was made within the continuation's delimited extent, then it moves with the continuation. If not, it'll be in the executing thread. In either case the answer is consistent: The value within the dynamic extent of the continuation is used. What happens to other threads if one overrides a parameter within its own dynamic scope? Nothing, threads don't have a dynamic scope relationship between them after thread creation.
- soegaard 6y agoI agree that Racket parameters work very well. FWIW the Racket parameters are inspired by: "Processes vs. User-Level Threads in SCSH" by Martin Gasbichler and Michael Sperber https://www.researchgate.net/publication/2546137_Processes_vs_User-Level_Threads_in_SCSH https://www.researchgate.net/publication/2546137_Processes_v...
- chrisseaton 6y ago> This isn't dynamic scoping; this is just a global variable with a stack of values. I don't know what you think dynamic scoping is? Because 'global variable with a stack of values' is what it is.
- tlb 6y agoSome implementations search up the stack for a binding. Which is slower, but works correctly with multithreading.
- chrisseaton 6y agoI'm not sure what you think is the important difference between these implementations? 'Dynamic binding' is an abstract concept. The abstract concept is a global variable with a stack of values. How you implement it and whether you share the call-stack instead of a separate stack is is up to you. It's still the same dynamic scoping.
- tlb 6y agoOnly one of them works with multiple threads. Dynamic scoping based on save-and-restore of symbol value slots is completely incompatible with multithreading. That's why Emacs still doesn't have threads.
- kazinator 6y agoThe deep binding approach will not work correctly with multithreading any more correctly than shallow binding, if there is only one such stack, or any shared state whatsoever that is not atomically manipulated. It will work if there is a spaghetti stack. So that is to say, each thread extends the dynamic environment with a newly allocated frame that points upward to the parent environment. Each thread needs a thread-specific pointer to the top of its own dynamic environment chain.
- tlb 6y agoWhat do you mean by a multithreaded runtime with only one stack?