cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 
JMP Wish List

We want to hear your ideas for improving JMP software.

  1. Search: Please search for an existing idea first before submitting a new idea.
  2. Submit: Post your new idea using the Suggest an Idea button. Please submit one actionable idea per post rather than a single post with multiple ideas.
  3. Kudo & Comment Kudo ideas you like, and comment to add to an idea.
  4. Subscribe: Follow the status of ideas you like. Refer to status definitions to understand where an idea is in its lifecycle. (You are automatically subscribed to ideas you've submitted or commented on.)

We consider several factors when looking for what ideas to add to JMP. This includes what will have the greatest benefit to our customers based on scope, needs and current resources. Product ideas help us decide what features to work on next. Additionally, we often look to ideas for inspiration on how to add value to developments already in our pipeline or enhancements to new or existing features.

Choose Language Hide Translation Bar

Keyword arguments for functions

I would love if in User Defined funcitons we could use keyword arguments in the calls so I can do

Names default to here(1);
f = function({x, y=2, z=3}, 
	show(x, y, z);
);
print("What I'd like to do");
f(1, z=2);
//I want it to be 1, 2, 2

//instead of having to do 
f = function({x, kwargs={}},
	{DEFAULT LOCAL}, 
	// optional
	y = 2;
	z = 3;
	evallist(kwargs);
	show(x, y, z);	
);
print("Using List");
f(1, {z=2});

Someone mentioned it here, but it was 8 years ago, so figured I'd bring it up again.  

15 Comments
gzmorgan0
Super User (Alumni)

Your current "kwargs" list solution is a nice interim trick. 

 

 

vince_faller
Super User (Alumni)

Thanks.  It definitely has a few problems. The biggest probably being able to inject any variables into a function and often causes variable collision I'm not careful.  

 

Names default to here(1);

f = function({x, kwargs={}},
	{DEFAULT LOCAL}, 
	// optional
	y = 2;
	z = 3;
	evallist(kwargs);
	print("Before Bad Call");
	show(y);
	print("Bad Call");
	//even though f2 is default local, 
	//because it's not explicit in the function it writes to here
	f2(1, {b=3, y=5}); 
	print("After Bad Call");
	show(y);
);

f2 = function({a, kwargs={}}, 
	{DEFAULT LOCAL}, 
	b=2;
	c=3;
	evallist(kwargs);
	show(a, b, c);
);

f(1, {z=2});

 

XanGregg
Staff

This would need a new syntax that wouldn't change existing scripts. Maybe something like your example with a list being passed to a special receiver. Or maybe a new syntax on the caller such as a fake namespace: f(1, arg:z = 2).

vince_faller
Super User (Alumni)

Why would a new syntax be required?    Are you saying for calling or for defining?  I think we could define them the same way.  

If a function already doesn't use keyword arguments then it would default to positional arguments like it currently does.  

Obviously we wouldn't be able to use keyword arguments before it's implemented so I don't see that as breaking scripts. 

 

The function

f = function({x, y=2, z=3}, 
	show(x, y, z);
);

 

wouldn't break any other scripts. 

and if all previous scripts are using positional arguments anyway then I don't think it would break anything.  

But if I there were keyword args that default to positional if no keywords were specified, doing

f(1, 2, 4);

wouldn't break anything (I'm guessing). But it would just allow 

f(1, z=4);

I don't see why it would need a fake namespace (but then again I don't understand how functions work under the hood).  

 

 

XanGregg
Staff

Currently arguments to user functions are evaluated and z=4 is a valid expression, so

f(1, z=4);

would evaluate z=4, creating a global z and having a result of 4, and then pass the result to the y argument of f.

vince_faller
Super User (Alumni)

Ahh.   So if someone used that syntax (for whatever reason) it would break if they needed a z global later on.  Got it.  

XanGregg
Staff

Right. Believe it or not, we do try not to break old JSL behaviors, even unlikely ones.  

gzmorgan0
Super User (Alumni)

@vince_faller, I still like your proposed kwargs={} solution with a modification.  IMHO Default Local is nice for simple scripting, but is risky for complex scripts.  I recommend managing a list of local variables. Anyway, below is a script that is a modification of your kwargs solution, but uses the syntax, {z,4} vs. z=4 as items in your kwargs list and requires a block of code that can be generic. A function could use a local associative array to assign "keyword" values which would implify the block of code noted below, however, then the individual variable syntax is more cumbersome, instead of a+b it would be something like  aa1[a] + aa1[b], so the exmaple uses lists.

 

if I had a function with many variables, but for each function call wanted to only change a few, I might use this approach. The key here is that

  • no variable gets changed unless it is in the varList, and
  • changed values are local.

 

Names Default To Here( 1 );

//simple example
g1 = Function({kwargs={}}, {Default Local},
  varList = {a,b,c,d,e,f};
  valList = {1,2,3,4,5,6};
  For(i=1, i<=nitems(kwargs), i++,
//each item must be a list of 2 values.
     If(IsList(kwargs[i]), 
     	Try(valList[loc(varList,kwargs[i][1])] = kwargs[i][2])
     );  	
  );
   Eval( EvalExpr( Expr(varList) = Expr(valList)) ) ;
   show(a,b,c,d,e,f);	
);

myList = {{a,10},{y,-44},{f,20}};
g1(myList); 
// [+] no variable gets changed unless it is in the variable list
// [-]  varList and valList need to be defined
// [-]  lines 7-13 would need to be added to each function, could be a global expression
// [-]  instead of z=4 syntax, {z,4} syntax is required 

//------Modification of Vince Faller script

f = function({x, kwargs={}},
	{DEFAULT LOCAL}, 
	// optional
	varList = {x, y};
	valList = {2,3};
	For(i=1, i<=nitems(kwargs), i++,
//each item must be a list of 2 values.
      If(IsList(kwargs[i]), 
     	Try(valList[loc(varList,kwargs[i][1])] = kwargs[i][2])
      );  	
    );
   Eval( EvalExpr( Expr(varList) = Expr(valList)) ) ;
	print("Before Bad Call");
	show(y);
	print("Bad Call");
	//even though f2 is default local, 
	//because it's not explicit in the function it writes to here
	f2(1, { {b,3}, {y,5}}); 
	print("After Bad Call");
	show(y);
);

f2 = function({a, kwargs={}}, 
	{DEFAULT LOCAL}, 
	varList= {b,c};
	valList = {2,3};
    For(i=1, i<=nitems(kwargs), i++,
//each item must be a list of 2 values.
      If(IsList(kwargs[i]), 
     	Try(valList[loc(varList,kwargs[i][1])] = kwargs[i][2])
      );  	
    );
   Eval( EvalExpr( Expr(varList) = Expr(valList)) ) ;
	show(a, b, c);
);

f(1, {z=2});

 

      

vince_faller
Super User (Alumni)

@gzmorgan0 My biggest problem with the kwargs list is that it allows anything to be executed. For instance if I want to see all of the locals in a given function. 

 

f = function({x, kwargs = {}}, 
	{y = 2, z = 3, a = Expr(y + z)}, 
	evallist(kwargs);
	r = x + y + z;
	return(r);
);

f(14, {print(namespace("local") << Get Contents)});

Which is a security problem for me with encrypted code.  

I actually keep things in an associative array and then do an << intersect with a default associative array to check that the passed arguments are valid. 

Then I just parse the key value pairs into the function so I don't have to use the array to call them.  

I have help functions to do all of this but that brings out its own set of problems.  And it's more cumbersome than I like.  

 

@XanGregg, my bad.  I have a much more narrow field of view on most of these things.  

vince_faller
Super User (Alumni)

@gzmorgan0 

 

I think I stole this from hamcrest but we've been using it for a while and it's a much better method of actually. You can still do some bad things but it checks a little better. 

 

Names default to here(1);
f = function({x, kwargs = {}}, 
	{DEFAULT LOCAL}, 
	// kwargs
	y = try(kwargs["y"], 2); // so the default is 2
	z = try(kwargs["z"], 3); // 3
	a = try(kwargs["a"], Expr(y+z)); // Expr(y+z)
	
	r = x + y + z + eval(a); 
	return(r);
);

show(f(2)); // 12
show(f(2, {y=14})); // 33
show(f(2, {wrong=14})); // still 12
show(f(2, {y=3, a=Expr(y^2)})); // 17