Re: Keyword arguments for functions@vince_faller
Cool example. Thanks for Sharing. Typo correction
show(f(2, {y=14})); // 36 not 33
Re: Keyword arguments for functions@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
Re: Keyword arguments for functions@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.
Re: Keyword arguments for functions@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});
Re: Keyword arguments for functionsRight. Believe it or not, we do try not to break old JSL behaviors, even unlikely ones.
Re: Keyword arguments for functionsAhh. So if someone used that syntax (for whatever reason) it would break if they needed a z global later on. Got it.
Re: Keyword arguments for functionsCurrently 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.
Re: Keyword arguments for functionsWhy 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).
Re: Keyword arguments for functionsThis 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).
Re: Keyword arguments for functionsThanks. 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});
Re: Keyword arguments for functionsYour current "kwargs" list solution is a nice interim trick.
Re: Keyword arguments for functionsHow are the standard Jmp functions defined,
- such that close accepts an supplementary argument noSave.
- such that add column accepts the expression Formula(...) at any position within (...)
dt << New Column( "X", Formula( row() ) );
Instead of
f(2, {y=14})could
f(2, y(14))
be the way how it should look like?
Re: Keyword arguments for functionsThe internal functions are built in C++, not JSL. The user functions (in JSL) are interpreted by other C++ code that chooses how to turn arguments into parameters (where the arguments to a function are the external values passed in and the function's parameters are the internal variable names that refer to the values.)
A function like sin(A), written in C++, looks something like
ExpressionPointer sin( ExpressionPointer E) {
double value = evaluateExpression( E );
double answer = sin( value );
return makeExpression( answer );
}
The expr() function in JSL, where the function just returns its argument without studying it, looks something like
ExpressionPointer expr( ExpressionPtr E ) {
return E;
}
That seem useless at first glance, but it allows the assignment operator to evaluate the right-hand-side of
ex = expr( a + b );
and store an expression tree into ex rather than the result of a+b. A more interesting example is the head(a+b) operator.
ExpressionPointer head( ExpressionPointer E ) {
return MakeExpression( E->child );
}
That's a crude approximation of the idea: an expression tree for a+b has a root, +, which has two children, a and b. E->child is getting the root, +, and returning that as an expression with no children.
The JMP developers made a decision to hide that complexity in the JSL user written functions to make the 99% use cases easy to write. Most of the languages I'm familiar with don't have expressions that can be manipulated (Snobol does, sort of, and that explains JMP's patmatch() function...) Using a list is a reasonable approach to sending an expression to a function; the list is evaluated...to the same list. The function is working on a copy of the list.
Keyword arguments for functionsI 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.
Automation and ScriptingRe: Keyword arguments for functions - Status changed to: AcknowledgedRe: Keyword arguments for functionsThe trick with the list is very useful.
Actually, evaluating the list is not really needed to access the variables.
One can scan the list and search for the right name. either by a for each loop or via Extract Expr.
Names default to here(1);
get = Function ({var}, Eval(Eval Expr(Arg(Extract Expr(kwarg, Expr(Name Expr(var)) = wild()),2))));
f = function({x, kwargs={}},
{DEFAULT LOCAL},
print(get(Expr(z)));
);
f(1, {z=2});