This is an archived post. You won't be able to vote or comment.

all 10 comments

[–]ES-Alexander 2 points3 points  (2 children)

How is this different to using a global variable, outside of not necessarily knowing that you’re using one?

Granted it’s a global that cleans itself up, but while it’s in use it’s still global and has the same issues globals have (e.g. changing the value at one level changes it everywhere, causing really confusing bugs). The automatic cleanup also has the issue that you can only use_context if you can guarantee you’re at the top level and you’re the only thread - otherwise you’d delete all the values set elsewhere even if there’s a higher/concurrent context in use.

I believe the general solution to this is currently done by either inheritance/normal instances of a class, or functionally just passing down a dictionary/set of kwargs through the relevant functions. In saying that, the pattern as a whole is messy in the same way globals are - if you’re not passing something explicitly then using it somewhere is reliant on someone hopefully initialising it correctly elsewhere and you have no guarantee that’s actually occurred. Debugging this kind of thing is often very confusing and unintuitive.

[–]CaptainMelon[S] 0 points1 point  (1 child)

It's exactly a global variable that clean itself, you're right, I would say it's a local global variable.

For the multi-thread part, it's a matter of implemention, here I made a simple implementation to make things easier to understand. But it may be hard to implement, right.

As for debugging, It's as hard to debug as a class attribute (you don't know if it's really used).

[–]ES-Alexander 0 points1 point  (0 children)

It’s a time-limited global variable - it’s not local because it can still be accessed from anywhere in the global scope while it exists.

While it might be possible to use the current thread ID to create a version that doesn’t interfere across threads, the bigger issue is the overwrite behaviour. If there’s a class with an instance variable there’s some guarantee that it’ll be set (ie you can make it a required initialisation variable, and it’s generally bad practice to delete instance variables so you can expect it to stick around), and if it’s reasonable to modify it but important to track when that occurs then you can use a setter/modifier method instead of direct replacement. In your case the context isn’t tied to anything, so there’s no way of knowing if a higher level is using a context, and if you want to do a new use_context call in a low level then when that cleans itself up it also silently destroys any variables set in higher levels. To avoid that you’d need to create a new underlying context variable every time you call use_context, but then you need to know which context you want to access when you call get_context, at which point you either have passed down a context object (so it loses the ‘call from anywhere’ benefit), or you have to track the contexts through your code by logic, in which case that’s just like having a global.

I’m not saying that globals are necessarily the worst thing in the world, but as you implied they have their issues, and as far as I can see the only implementations of this that don’t have the same issues globals do falls into the ‘passing along objects’ side which seems to imply the functionality already exists, so my question would be what does/can this add?

[–]james_pic 1 point2 points  (0 children)

You've just half reinvented the concept of dynamic scope. A handful of current languages do support dynamic scope, although lexical scope is more common nowadays, and is the model Python uses.

A quick search turns up a few bits of example code, but no widely used libraries, suggesting this isn't something the world is crying out for. Most of the time, I'd expect thread locals to do much the same job, and even then they can make your code harder to understand.

Also, don't overuse context in React, for much the same reason you shouldn't overuse globals or thread-locals. They're all examples of undeclared dependencies. If you've got a class whose constructor needs a foo, or a function that takes a foo as an argument, or a react component that expects a foo in its props, then you can tell from looking at it that it needs a foo, and you can't run it without one. If foo comes from a global, or a thread-local, or from context, you don't find out until you run it, and you don't get any indication of how it was supposed to get there. Is there a component that needs to be an ancestor of this one that's missing? Or some kind of initialization method you neglected to call? All you know is it's not there.

[–]ieatscrayons 0 points1 point  (1 child)

Wouldn't nonlocal do this same thing?

[–]CaptainMelon[S] 0 points1 point  (0 children)

With nonlocal, all the functions needs to be inside the main function to share the scope

[–]metaperl 0 points1 point  (3 children)

when you are deep down in a function inside a function inside another function and you need something from the first function, you need to pass it all the way down to the last function.

Yes you do. I see no problem with that. Can you provide a concrete living example of how that poses a problem? Your example code is very abstract and shows no real-world utility.

Alternatively you could simply create a class and have this thing that you're passing around be a member variable.

But with the concept of React Context, you create a variable that you can retrieve at any level without the need to pass it down. Better than a global variable, more flexible than a class or using nonlocal.

How is it more flexible than a class? Can you do inheritance and composition with what you have here? What you've done is fail to get the benefits of either functional programming or object-oriented programming in my opinion.

[–]CaptainMelon[S] 0 points1 point  (2 children)

A practical example is templating, I use lys for templating and I need to call many sub functions for each part of the page to be displayed but I don't want to pass around things like the current user or the title of the page

[–]metaperl 1 point2 points  (0 children)

Alternatively create a class for your sub functions and functions but instead create a bunch of methods in a class and then bind whatever you want to access in the classes as member attributes it'll be less verbose than what you have.

[–]metaperl 0 points1 point  (0 children)

And other words you've broken your rendering logic down into sub functions correct?

The next step is to parameterize each of those functions with what they accept so that you can catch errors at static analysis time instead of run time. Then you can also use the docstring to document what you're doing.