3.3.5: About variable scopes

Version 3.3.5 of G’MIC introduces an interesting change that concerns the scope of variables in commands.

Until now, In G’MIC, you had two different kinds of variables :

  • Local variables, like var=1976, which were only accessible in the command they are defined in.
  • Global variables, like _var=1976, which were accessible everywhere.

G’MIC 3.3.5 will introduce the new concept of subcommands : A subcommand of a command foo is a command whose name starts with _foo (e.g. _foo_sub), and which is called from command foo. A subcommand has these interesting properties (which are not true in a non-subcommand of course):

  • In a subcommand, you have access to local variables defined in the calling command. So,
foo : 
  var=1976
  _foo_sub

_foo_sub : 
  echo "var  = "$var

Calling foo will print var = 1976.

  • In a subcommand, the variables you define can be accessed from the calling command too:
foo : 
  _foo_sub
  echo "var = "$var

_foo_sub : 
  var=1976

So, here again, calling foo will print var = 1976.

I realized that this concept of subcommand is useful, while I was reimplementing the parsing of primitive materials to read .obj files (3D meshes). My parser of the material file was actually passing materials properties to the input_obj command by defining global variables for each primitive. Meaning, a lot of global variables were defined, when the 3D mesh has a large number of primitives (it often has more than 100k primitives…).

And having more than 100k global variables defined is not really cool :slight_smile: Global variables cannot be “unset” (their values can be emptied, but not the variable name definition itself). And that was slowing down the access to other global variables in the rest of the G’MIC pipeline.

So now, I’m able to define my material parsing procedure as a subcommand of input_obj, which defines only ‘local’ variables, and once the 3D mesh is read, those local variables are freed.

Subcommands are then useful when you have a lot of variables to pass from one command to another one (so much variables than passing them as command arguments would be painful).

That’s all the G’MIC progress news for the weekend :slight_smile: Have a good Sunday!

1 Like

Good stuff. Can subcommands have subcommands? So we could have foo, _foo_something and __foo_something_else?

If so, then foo and _foo_something would share scope. And _foo_something and __foo_something_else would also share scope. Do foo and __foo_something_else also share scope?

Yes:

foo :
  _foo_sub
  e "VAR = "$var

_foo_sub :
  __foo_sub_sub

__foo_sub_sub :
  var=1976

will print VAR = 1976.

Not directly, no. _foo_something has to call __foo_something_else to share its variable with foo.
So:

foo : 
  __foo_sub_sub 
  e "VAR = "$var

__foo_sub_sub : 
  var=1976

will print VAR = , because __foo_sub_sub is not a subcommand of foo.

Subcommands define a “one-level deep” sharing of variable, that can be propagated to the parent command through consecutive calls. But it cannot “jump” several levels of calling at once.

1 Like

That’s clear, thanks.

1 Like

Must the calling command execute the subcommand first before subcommand local variables are visible to the caller?

That is (to modify your example)

foo : 
 # _foo_sub
  echo "var = "$var

_foo_sub : 
  var=1976

is an error.
Seems that this must be so, but I am being particular.

Yes, a subcommand must be called explicitely by the parent command.
The interpreter won’t take the decision to call the subcommand automatically, and fortunately so! If the call of a subcommand was automatically done, you couldn’t control precisely if and when you want your subcommand to do something (defining variable is one thing, but a subcommand can do anything a regular command does).

1 Like

Now, I have to wonder, does this break anything in gmic-community? I’m going to have a long look at my file. If anything breaks, __ would replace _, easy fix.

I noticed +_command in my file, does it detect that as subcommand?

There’s really not much chance that it breaks something.

Good feature. Thanks for the update.

Yes, a command defined with +_command : sth is a subcommand of command as well.

Last question, what about command that has been made with m/command? I can use local variables here to replace the use of global variables? That’d make refactoring some of my interactive filters so much easier.

Yes, using command command (aka m) to define subcommands also works:

foo :
  m "_foo_sub : var1,var2=1976,2024"
  _foo_sub
  e $var1,$var2

A subcommand is detected as such when it is called and not before (and the interpreter just checks the command name to decide whether it’s a subcommand or not).

1 Like

Will this be compatible with previous G’mic 3.3.x versions?

No, that’s a core 3.3.5 feature. That’s why I’ve stopped the filter updates for 3.3.4, and I’ll probably release 3.3.5 soon.
Today, I’ve uploaded new binaries of 3.3.5_pre so you can test it if you wish.

2 Likes

This gave me a idea to finally be able to create dictionary.

foo:
_foo ${-_foo_ids}
echo $d1 # prints crash
_foo: $=d
_foo_ids:u crash,dummy,tester,leethack

Or this:

foo:
m _foo:"$""=d"
_foo ${-_foo_ids}
echo $d1 # prints crash
_foo_ids:u crash,dummy,tester,leethack

So, to retrieve the value, It’s ${d$key}.

Tested N°2 and it works for me. How do you define the key as a string?

Keys are in this case only consists of numbers. So, in my example, $d1,$d2,$d3,$4= crash,dummy,tester,leethack. So you can retrieve them by that or by ${d$key}. The second one is more valuable if you need to use 0 to get first value as you do ${d{$key%4+1}}.

Also, d can be anything you want. I just use it short for dictionary because at its very essence, it is a dictionary.

Wait, you wanted to use strings instead of numbers?

Ok, then it’s a bit more like a list? Or is the key really associated to the value if you change the order in _foo_ids?

Yes, like python dictionnaries? I thought that’s what you were doing.

The key is associated with the order of the value.

In the string as key case, it’d be this:

foo:
list_of_strings_key=a,b,c,d
$list_of_strings_key:=expr('x+1',narg($list_of_strings_key))
m _foo:"$""=d"
_foo ${-_foo_ids}
echo ${d$c} # prints tester
_foo_ids:u crash,dummy,tester,leethack