cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 
Check out the JMP® Marketplace featured Capability Explorer add-in
Choose Language Hide Translation Bar
miguello
Level VI

Need help understanding how variables are scoped in expressions

Hello all, 

 

I have a simple example script. Can somebody explain me what are the scopes for the variables here and why it works one way and not another?

 

Here's the initial script:

Names default to here(1);
Clear Globals();
x="Variable";

exprA = Expr(Write("Here is variable: "||x));
exprB = Expr(
dt1 = Open( "$SAMPLE_DATA/Big Class.jmp" );dt1 << OnClose(exprA));

exprB;

The idea is that I open table and give it an expression it needs to run when it's closed. Expression evaluates and changes (not in this example) some variables from the script.

The way it is written it doesn't work - nothing happens, but in my real script it just says it can't find a reference.

Okay, let's try to make ExprA global:

Names default to here(1);
Clear Globals();
x="Variable";

::exprA = Expr(Write("Here is variable: "||x));
exprB = Expr(
dt1 = Open( "$SAMPLE_DATA/Big Class.jmp" );dt1 << OnClose(exprA));

exprB;

Now it does see the expression, but it still doesn't work, because now it doesn't see the x variable.

Ok, let's make x global too:

Names default to here(1);
Clear Globals();
::x="Variable";

::exprA = Expr(Write("Here is variable: "||x));
exprB = Expr(
dt1 = Open( "$SAMPLE_DATA/Big Class.jmp" );dt1 << OnClose(exprA));

exprB;

Now it works:

Scriptable[]Here is variable: Variable

// Close Data Table: Big Class
Close( "Big Class" );

The question: why does it work this way and doesn't work the initial way? I looked to me it should've worked.

I don't like using global variables in this script - any other way to make it work?

 

 

 

 

1 ACCEPTED SOLUTION

Accepted Solutions
ErraticAttack
Level VI

Re: Need help understanding how variables are scoped in expressions

Scoping goes like this  -- lookup in Local() namespace if it exists, if not lookup in Here() namespace if it exists, if not lookup in Global() namespace.  (Also, it will check the column names of the current data table sometimes as well).

 

What you're doing in your first script is to put the name "x"n into the Here() namespace with value "Variable", then put the name "exprA"n into the Here() namespace with value Write( "Here is variable: " || x ), then put name "exprB"n into the Here() namespace with value Glue( dt1 = Open( "$SAMPLE_DATA/Big Class.jmp" ), dt1 << On Close( exprA ) ).

 

Finally you evaluate "exprB"n -- JMP then tries to resolve this name by first checking the Local() namespace (it doesn't exist), then the Here() namespace and it finds it -- so it runs the expression stored in As Scoped( "here", "exprB" ).

 

This then loads the table and puts exprA as the closing script to the window.

 

Since this is the end of the script then the Here() namespace created for the script is deleted and the memory cleared.

 

When you close the table, it finds the literal exprA as the closing script, then it tries to scope the name as above (in Local()Here()Global()), only this time there is no Here() namespace and it cannot find the name in any of the three namespaces, thus throwing an error.

 

In general, when you want to add a variable as a static value to an expression that will run in a different scope, use either Eval( Eval Expr( ...; Expr( Name Expr( val ) ); ... ) ) or some other equivalent.

 

In my personal experience I create explicit namespaces for each script (anonymous) then manually scope all variables to the respective namespace.  I can then pass this namespace around as needed (and of course I clear and close the namespace when the script is done / window is closed).

 

Some examples of how your script could be made to work are as follows:

Names Default To Here( 1 );
x = "Variable";

Eval( Eval Expr(
	dt1 = Open( "$SAMPLE_DATA/Big Class.jmp" );
	dt1 << OnClose( Write( "\!NHere is variable: " || Expr( x ) ) );
) );

or

Names Default To Here( 1 );
x = "Variable";

exprA = Expr(
	Write( "\!NHere is variable: " || x )
);
Eval( Eval Expr(
	dt1 = Open( "$SAMPLE_DATA/Big Class.jmp" );
	dt1 << OnClose( x = "rather"; Expr( Name Expr( exprA ) ) );
) );

or

Names Default To Here( 1 );
self = New Namespace();
self:x = "Variable";

self:exprA = Expr(
	Write( "\!NHere is variable: " || self:x )
);
Eval( Eval Expr(
	dt1 = Open( "$SAMPLE_DATA/Big Class.jmp" );
	dt1 << OnClose(
		Local( {self = Namespace( Expr( self << Get Name ) )},
			self:exprA;
			Delete Namespaces( Force( 1 ), self )
		)
	);
) );

Note that while many programming languages create local namespaces and keep them around using reference counters until they're no longer needed (such as Python with functions and such), JSL doesn't really do that and it is up to the coder to maintain any persistence and scoping needed. The code inside the <<On Close( ... ) message is evaluated in a completely separate context to the script that created it with no management of names or scopes.

 

Hope this helps, it can be a bit confusing!!

 

Jordan

View solution in original post

3 REPLIES 3
ErraticAttack
Level VI

Re: Need help understanding how variables are scoped in expressions

Scoping goes like this  -- lookup in Local() namespace if it exists, if not lookup in Here() namespace if it exists, if not lookup in Global() namespace.  (Also, it will check the column names of the current data table sometimes as well).

 

What you're doing in your first script is to put the name "x"n into the Here() namespace with value "Variable", then put the name "exprA"n into the Here() namespace with value Write( "Here is variable: " || x ), then put name "exprB"n into the Here() namespace with value Glue( dt1 = Open( "$SAMPLE_DATA/Big Class.jmp" ), dt1 << On Close( exprA ) ).

 

Finally you evaluate "exprB"n -- JMP then tries to resolve this name by first checking the Local() namespace (it doesn't exist), then the Here() namespace and it finds it -- so it runs the expression stored in As Scoped( "here", "exprB" ).

 

This then loads the table and puts exprA as the closing script to the window.

 

Since this is the end of the script then the Here() namespace created for the script is deleted and the memory cleared.

 

When you close the table, it finds the literal exprA as the closing script, then it tries to scope the name as above (in Local()Here()Global()), only this time there is no Here() namespace and it cannot find the name in any of the three namespaces, thus throwing an error.

 

In general, when you want to add a variable as a static value to an expression that will run in a different scope, use either Eval( Eval Expr( ...; Expr( Name Expr( val ) ); ... ) ) or some other equivalent.

 

In my personal experience I create explicit namespaces for each script (anonymous) then manually scope all variables to the respective namespace.  I can then pass this namespace around as needed (and of course I clear and close the namespace when the script is done / window is closed).

 

Some examples of how your script could be made to work are as follows:

Names Default To Here( 1 );
x = "Variable";

Eval( Eval Expr(
	dt1 = Open( "$SAMPLE_DATA/Big Class.jmp" );
	dt1 << OnClose( Write( "\!NHere is variable: " || Expr( x ) ) );
) );

or

Names Default To Here( 1 );
x = "Variable";

exprA = Expr(
	Write( "\!NHere is variable: " || x )
);
Eval( Eval Expr(
	dt1 = Open( "$SAMPLE_DATA/Big Class.jmp" );
	dt1 << OnClose( x = "rather"; Expr( Name Expr( exprA ) ) );
) );

or

Names Default To Here( 1 );
self = New Namespace();
self:x = "Variable";

self:exprA = Expr(
	Write( "\!NHere is variable: " || self:x )
);
Eval( Eval Expr(
	dt1 = Open( "$SAMPLE_DATA/Big Class.jmp" );
	dt1 << OnClose(
		Local( {self = Namespace( Expr( self << Get Name ) )},
			self:exprA;
			Delete Namespaces( Force( 1 ), self )
		)
	);
) );

Note that while many programming languages create local namespaces and keep them around using reference counters until they're no longer needed (such as Python with functions and such), JSL doesn't really do that and it is up to the coder to maintain any persistence and scoping needed. The code inside the <<On Close( ... ) message is evaluated in a completely separate context to the script that created it with no management of names or scopes.

 

Hope this helps, it can be a bit confusing!!

 

Jordan
miguello
Level VI

Re: Need help understanding how variables are scoped in expressions

Jordan, thanks a lot, it does explain the behavior. I do like your proposed way with namespaces, since the real script has many tables that would share variables in their OnClose statements. 

Can you help me understand the following piece of code:

		Local( {self = Namespace( Expr( self << Get Name ) )},
			self:exprA;
			Delete Namespaces( Force( 1 ), self )
		)

Local resolves names to local variables, what does that mean? Why do you need to define some variables in curly brackets, what's the purpose? (both Scripting index and documentation don't say much about it). 

 

 

ErraticAttack
Level VI

Re: Need help understanding how variables are scoped in expressions

Short answer -- it keeps names from overwriting anything outside the Local() block.

 

Long answer:

For my functions I write them like this:

some function = Function( {arg1, arg2, etc},
	{Default Local},
	
	self:arg1 = arg1;
	self:arg2 = arg2;
	
	//do something amazing!
	//call a method:
	self:call method
);
call method = Function( {},
	//do something else amazing!
	1
)

 

I then use meta-programming to define self and inject a reference into each function, which transforms it to something like:

self:some function = Function( {arg1, arg2, etc},
	{Default Local},
	self = Namespace( "#48" );
	
	self:arg1 = arg1;
	self:arg2 = arg2;
	
	//do something amazing!
	//call a method:
	self:call method
);
self:call method = Function( {},
	{self},
	self = Namespace( "#48" );
	//do something else amazing!
	1
)

This creates something that is very similar to Python classes (my code is very much different from the above statements, but conceptually similar).  The thing that I want to never happen is to have the reference self be overwritten by any other value.

 

Imagine that in a function I have a Wait( 1 ) function for some reason, and during that wait phase someone closes a table that has a script attached that redefines self -- when the wait statement is done then self now refers to a different variable and chaos ensues.

 

The Local() function creates a local block and any variables defined in the first argument (the list) are local to that block, such as

 

Names Default to Here( 1 );
a = 1;
b = 4;
Print( "Before Local Block:" );
Show( a, b );
Local(
	{local_var1, local_var2, b},
	local_var1 = 4;
	local_var2 = 2;
	b = 9;
	Print( "Inside Local Block:" );
	Show( local_var1, local_var2, a, b );
	a = 5;
);
Print( "After Local Block:" );
Show( Try( local_var1, Empty() ), Try( local_var2, Empty() ), a, b )
/*:

"Before Local Block:"
a = 1;
b = 4;
"Inside Local Block:"
local_var1 = 4;
local_var2 = 2;
a = 1;
b = 9;
"After Local Block:"
Try(local_var1, Empty()) = Empty();
Try(local_var2, Empty()) = Empty();
a = 5;
b = 4;

Here I redefine both a and b inside the local block, but b outside the local block is not affected while a is.  Putting self as a local variable is the safest approach.

 

Jordan