Live data from Hacker News

Dynamic Scoping in C++

blog.dokucode.de

51–60 of 64 posts

Re: Dynamic Scoping in C++

#51
post #7

I don’t see how this is functionally different to passing an object that contains references to the relate to variables; which I’ll call a context object. Practically, dynamic scoping is more confusing than context objects. void main() { int x = 2; fn(); } Does fn access or change x? You need to inspect the body of fn to know. I would call dynamic scoping a poor form of coupling. Instead of bundling your coupling wir…

> I don’t see how this is functionally different to passing an object that contains references to the relate to variables; which I’ll call a context object. It's the same difference as using environment variables vs command line arguments. Imagine all programs having to pass TERM, DISPLAY, HOME, etc. as arguments in case some descendant process wants to use it and the user override. Like passing TERM, DISPLAY to git…

> Imagine all programs having to pass TERM, DISPLAY, HOME, etc. as arguments in case some descendant process wants to use it and the user override. Like passing TERM, DISPLAY to git in case you want to override them for the configured pager or editor.

Interestingly this is exactly what I have been wishing was standard practice for a few years now! Otherwise you end up pickup up implicit configuration from the environment that you didn't intend at all.

> In other words, the issue is that when you have project A using project B using project C, project A has to manually carry around the context of B, and C in case the user wants to override them.

Yes please! That makes the most sense to me. I admit my years of experience with Haskell may have coloured this opinion.

Re: Dynamic Scoping in C++

#52
post #2

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.

Thread 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.

The 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.)

Re: Dynamic Scoping in C++

#53
post #39

Earlier quoted context omitted.

Yeah, “12 factor apps” basically use dynamic scope for dependency injection. But, also, I believe the Reader typeclass in Haskell is basically equivalent to dynamic scope? Haskellers seem to use this and a lot of similar techniques (Free monads/mtl style) to work around the lack of dynamic scope :).

Reader is similar, but I wouldn't say equivalent, because only those things working in the Reader can use that environment. For example, when you have project A using project B using project C, you can't define a putStrLn such that project C can use it and be able to override its output stream from project A, while keeping B completely ignorant of that ability of putStrLn or that C even uses putStrLn. Reader is like…

Reader is like implicit parameters: dynamic scoping with static types. https://dl.acm.org/doi/10.1145/325694.325708

Re: Dynamic Scoping in C++

#54
post #2

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.

> 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.

No, that's how it is implemented by a compiler; what makes dynamic scoping "scoping" is that it related to how the variables are lexically organized. It is like claiming you are adding "classes" to a language but then merely providing an object-orientation runtime library akin to like, the Objective-C C runtime. You actually could design a system of a bunch of macros in C to have something like classes, but the low-level mechanism is not that. If you wanted to build something that was dynamic scoping in C++ I would (for avoidance of doubt, this is not what I was saying in my original comment) use thread local storage with a global map (and put the name as a string or something, maybe as a C++2y string template parameter) so that you didn't have to define the variable in the global scope. Because what could dynamic scoping possibly mean if you are literally having to type the variable into the global scope?

You really are confusing the implementation of dynamic scoping with what dynamic scoping is: the entire point of having that term at all is to describe how the variables are scoped not how they are set. If you have to type the name of the variable into the global scope, then obviously it isn't dynamically scoped.

Re: Dynamic Scoping in C++

#55
post #54

Earlier quoted context omitted.

> 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.

No, that's how it is implemented by a compiler; what makes dynamic scoping "scoping" is that it related to how the variables are lexically organized. It is like claiming you are adding "classes" to a language but then merely providing an object-orientation runtime library akin to like, the Objective-C C runtime. You actually could design a system of a bunch of macros in C to have something like classes, but the low-l…

> No, that's how it is implemented by a compiler

What do you think the call stack is?

The only difference between what you're thinking of and what I'm thinking of is you're row-based and I'm columnar-based. Why is that such an important difference?

Re: Dynamic Scoping in C++

#56
post #2

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.

IMO, 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 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 comp…

I 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_v...

Re: Dynamic Scoping in C++

#57
post #51
post #7

Earlier quoted context omitted.

> I don’t see how this is functionally different to passing an object that contains references to the relate to variables; which I’ll call a context object. It's the same difference as using environment variables vs command line arguments. Imagine all programs having to pass TERM, DISPLAY, HOME, etc. as arguments in case some descendant process wants to use it and the user override. Like passing TERM, DISPLAY to git…

> Imagine all programs having to pass TERM, DISPLAY, HOME, etc. as arguments in case some descendant process wants to use it and the user override. Like passing TERM, DISPLAY to git in case you want to override them for the configured pager or editor. Interestingly this is exactly what I have been wishing was standard practice for a few years now! Otherwise you end up pickup up implicit configuration from the environ…

> Interestingly this is exactly what I have been wishing was standard practice for a few years now!

You mean that when you call e.g. `tmux` you want to be forced to call it as something like `tmux -e HOME=... -e TERM=... -e DISPLAY=... -e USER=... ...`, and likewise for basically all other programs?

> I admit my years of experience with Haskell may have coloured this opinion.

Reader in Haskell helps prevent having to do that similar to environment variables or dynamic scoping, and is really, really common to use it as such instead of having to pass arguments around, so...

Re: Dynamic Scoping in C++

#58
post #53
post #39

Earlier quoted context omitted.

Reader is similar, but I wouldn't say equivalent, because only those things working in the Reader can use that environment. For example, when you have project A using project B using project C, you can't define a putStrLn such that project C can use it and be able to override its output stream from project A, while keeping B completely ignorant of that ability of putStrLn or that C even uses putStrLn. Reader is like…

Reader is like implicit parameters: dynamic scoping with static types. https://dl.acm.org/doi/10.1145/325694.325708

I know what Reader is, but my point still stands.

Re: Dynamic Scoping in C++

#59
post #18

Earlier quoted context omitted.

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. 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?…

> 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 a…

To add to what I said yesterday, focusing on this question:

> Is there any way in which doing so via "dynamic scoping" makes it any less of a horrible idea?

In both types of languages it's a horrible thing to set a global variable, because the modification is visible to all code that runs later. If you want to know what value a global variable would have in a function, you'd need to be aware of all code that executed before.

In lexically-scoped languages, you can only read or set a variable. If you forbid setting it, you can only read it. In dynamically-scoped languages you can not only read or set a variable, you can also shadow it. Shadowing is not as bad as setting, because the "modification" is scoped to a call. Because of that, it doesn't undergo changes across functions that don't have a caller/callee relationship (e.g. cousin functions in a call-graph), and I believe that's where the real devil in modifying global variables lies, in reading a variable that could have been modified by functions that don't have a clear relationship with the current one.

Having said that, shadowing has its place. It certainly can be misused like any other feature, but I'm just answering why it's less of a horrible idea, as you put it. Though, it certainly has its benefit with no equal. Not even Haskell's Reader typeclass, because of its static-typed nature, offers exactly the same ability to modify the behavior of any callee-descendant function, unforeseen by the author of the function you're calling.

It occurs to me that this is also similar to the ability in OOP to inherit from a class or module and overriding one of its methods, only that the change, instead of being scoped to the callee-descendants, is scoped to the other methods (and their callers) that use the overriden method.

Re: Dynamic Scoping in C++

#60
post #57
post #51

Earlier quoted context omitted.

> Imagine all programs having to pass TERM, DISPLAY, HOME, etc. as arguments in case some descendant process wants to use it and the user override. Like passing TERM, DISPLAY to git in case you want to override them for the configured pager or editor. Interestingly this is exactly what I have been wishing was standard practice for a few years now! Otherwise you end up pickup up implicit configuration from the environ…

> Interestingly this is exactly what I have been wishing was standard practice for a few years now! You mean that when you call e.g. `tmux` you want to be forced to call it as something like `tmux -e HOME=... -e TERM=... -e DISPLAY=... -e USER=... ...`, and likewise for basically all other programs? > I admit my years of experience with Haskell may have coloured this opinion. Reader in Haskell helps prevent having to…

> Reader in Haskell helps prevent having to do that similar to environment variables or dynamic scoping

Reader in Haskell addresses some of the same concerns, but it differs in important ways. Reader is statically determined, Reader is clearly scoped to particular parts of your program (you can see whether a given function's behavior might depend on a value from a Reader in a way that's not visible with dynamic scope or environment variables), and Reader forces you to have set everything you might access. You could work around that last by making it a `Reader (Map String Dynamic)` or something, but that's not common in Haskell.

I make no claim, here, that these are obviously the right decisions - but they seem to provide enough differentiation that someone could rationally prefer one or the other.

Post reply on HN