Enviroment and ~parent: envirPut/envirGet asymmetry

Ok, maybe we’re tapping somewhere where we shouldn’t… but I’m designing an interface for an object that would really like to be able to access its parent environment as ~parent.

I understand that when know:true, setting ~parent in an Environment will set the parent for real:

a = Environment(parent:currentEnvironment, know: true);
a.use { ~parent = (test: \ok) };
a.test // -> ok

But then why doesn’t envirGet behave the same?

a = Environment(parent:currentEnvironment, know: true);
a.use { ~parent } // -> nil

And so it doesn’t matter if I try to do a[\parent] = something: if know is true, put will set the parent “instance variable”, and envirGet will still not get it, while at does though.

a = IdentityDictionary(know: true)
a[\parent] = (test: \ok)
a[\parent] // -> (test: ok)
a.use { ~parent } // -> nil

So it looks like envirPut and envirGet are not “symmetric” in this.

Looking at the source code in LangPrimSource, envirPut calls identDictPut, which checks for know and parent and writes to a specific slot:

And prIdentDict_At also does the same. But envirGet doesn’t call it, calling instead identDict_lookup, which doesn’t check for know and “special fields” at all.

Does anyone know more about this?

1 Like

Environment help says:

The compiler provides a shortcut syntax where ~ is a placeholder for currentEnvironment. This makes the expression ~myvariable; equivalent to currentEnvironment.at(\myvariable); and the expression ~myvariable = 888; equivalent to currentEnvironment.put(\myvariable, 888);

If this is true, then it’s a mistake in the envirGet primitive.

It isn’t clear from git history who wrote this, though. If that’s originally from James McCartney, I’d take it to be definitive. If somebody added it later, they might have assumed it should be equivalent without thoroughly testing. I.e., who knows?

I think it probably should be changed, though.

hjh

thanks @jamshark70, I was actually really hoping you would give me some input on this <3

well, I might be misunderstanding, but looking at Bison, I see ~ being translated to envirGet and envirPut:

This bison code is from ‘initial revision’ 20 years ago (James McCartney committed on Sep 6, 2002). I looked at some more old JMC commits to shine some light on intentions here:

19 years ago, in this commit (replace envirGet and envirPut with primitives · supercollider/supercollider@0071e93 · GitHub)
envirPut is calling identDictPut, but envirGet is still not calling prIdentDict_At. However, both prIdentDict_At and envirGet are calling the same function: arrayAtIdentityHashInPairs.
(note: there is also identDictAt but it’s unused and will disappear later on)

18 years ago, IdentDicts with know==true will treat parent and proto as properties · supercollider/supercollider@3c641e8 · GitHub is where prIdentDict_At and identDictPut get the behavior we’re discussing.
prIdentDict_At diverges from envirGet: it gets the extra checks for parent and proto when know==true, and then it calls identDict_lookup.
envirGet doesn’t get the check, and looks like it duplicates codes from identDict_lookup for the most part.

Fast-forward to today, that “code duplication” is gone, envirGet calls identDict_lookup. So, envirGet and prIdentDict_At are almost identical, with the first having an extra check for currentEnvironment and the latter having the extra check for know.

Even if I don’t feel too confident changing stuff at this level, I have a strong feeling for envirGet having to call prIdentDict_At. Actually, I would restore identDictAt and call it from both prIdentDict_At (which is meant to be a primitive for IdentityDictionary) and envirGet (primitive for Symbol).

I actually did it in this branch of mine, if anyone wants to have a look or give it a try: GitHub - elgiano/supercollider at topic/envirGet

Ok, back to music now :slight_smile:

1 Like