cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 
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