Bug. Cannot use writeln to print the result of a lambda.
proc doSomething() {
return 42;
}
proc main() {
writeln(lambda() { return doSomething(); }); // should print "42"
}
# Output
_chpl_lambda_0()
chpl version 1.21.0 pre-release (b2b1b701)
This behavior occurs because it's printing a function identifier. I wonder whether that's the right thing to do. For example, a more exhaustive code:
proc fn1() { return 1; }
proc fn2 { return 2; }
var fn3 = 3;
var fn4 = lambda() { return 4; };
proc main() {
writeln(fn1); // fn1()
writeln(fn2); // 2
writeln(fn3); // 3
writeln(fn4); // _chpl_lambda_0()
}
The behavior of writeln(fn1) is not uniform with fn2 and seems like it could be potentially confusing in a refactoring effort. I'd lean towards disallowing writeln(fn1); it'll be the most problematic if fn1() returned a string that was used in program logic.
I would expect this behavior:
proc main() {
//writeln(fn1); // Illegal
writeln(fn2); // 2
writeln(fn3); // 3
//writeln(fn4); // Illegal; must call with fn4()
}
fn4 isn't the same as the parent problem of writeln(lambda() { return 5; }), so I'm not quite sure what to do. It seems reasonable to require named lvalue lambdas to be explicitly called and to have rvalue lambdas be evaluated. Or I could be wrong.
@BryantLam - writeln(lambda() { return doSomething(); }); is similar to writeln( fn1 ). The difference is just that lambda() { ... } declares an anonymous function with no arguments.
If you want to evaluate your function and print out the result, you would write writeln( fn1() ) or similarly writeln(lambda() { return doSomething(); } () ). Which works today.
Later, you suggest that writeln( fn1 ) should be an error. But, I don't see what issue making it an error would help solve. Generally speaking I think it's useful to be able to print out something useful for each kind of type in the language (even if we can't do things like read in a first-class-function (FCF) ).
It is true that fn2/fn3 will "evaluate" when printed. But, fn3 is just a variable (despite its name) and so we wouldn't expect writeln(fn3) to do anything different. And fn2 is a paretheses-less function and the parentheses-less function feature is designed to make the function behave similarly to a variable/field. So, it is evaluated upon fn2 even without no ().
The reason that writeln( fn1 ) behaves differently is that it is capturing fn1 as a FCF. It doesn't make sense to "capture" a variable in this way, so the behavior will be different from fn3. At the same time, what is special about writeln ? Are you saying that you don't think one should be able to ever pass a function to another function? That would seem to miss one of the design goals of FCFs even if we have significant open questions in that area.
Tagging @krishnadey30 and @LouisJenkinsCS on this since they were champions of this change on issue #13210.
I agree with @mppf; the only thing that I can see a valid argument for is whether writeln taking an unapplied function requiring arguments should require an explicit cast to string since I assume it to be treated similar to a type.
If you want to evaluate your function and print out the result, you would write
writeln( fn1() )or similarlywriteln(lambda() { return doSomething(); } () ). Which works today.
The reason that
writeln( fn1 )behaves differently is that it is capturingfn1as a FCF. It doesn't make sense to "capture" a variable in this way, so the behavior will be different fromfn3.
Okay that makes sense. I guess I'm a bit weary if the original code was:
proc myFunc {
return "Hello!";
}
writeln(myFunc); // prints "Hello!"
that was later refactored into a regular procedure without changing all instances of its use (hidden API break), which may not be possible if the call occurred in client code.
proc myFunc() {
return "Hello!";
}
writeln(myFunc); // prints "myFunc()". Whoops!
This seems hard to debug. I suppose this is just a consequence of having property procedures.
Are you saying that you don't think one should be able to ever pass a function to another function?
That's a good point. I'm okay being wrong on this issue. Feel free to close out if there's no more discussion.
@BryantLam: It seems like the property you cite would be true of all calls to myFunc. That is, everywhere you were doing something like var x = myFunc; you'd now have to add the parenthesis to not completely change the meaning of the program (x was a string, now it's a FCF). As you say, I think this is just the nature of the feature and don't know of any way around it apart from jettisoning it altogether (which I'd rather not do).
Most helpful comment
@BryantLam -
writeln(lambda() { return doSomething(); });is similar towriteln( fn1 ). The difference is just thatlambda() { ... }declares an anonymous function with no arguments.If you want to evaluate your function and print out the result, you would write
writeln( fn1() )or similarlywriteln(lambda() { return doSomething(); } () ). Which works today.Later, you suggest that
writeln( fn1 )should be an error. But, I don't see what issue making it an error would help solve. Generally speaking I think it's useful to be able to print out something useful for each kind of type in the language (even if we can't do things like read in a first-class-function (FCF) ).It is true that fn2/fn3 will "evaluate" when printed. But, fn3 is just a variable (despite its name) and so we wouldn't expect
writeln(fn3)to do anything different. And fn2 is a paretheses-less function and the parentheses-less function feature is designed to make the function behave similarly to a variable/field. So, it is evaluated uponfn2even without no().The reason that
writeln( fn1 )behaves differently is that it is capturingfn1as a FCF. It doesn't make sense to "capture" a variable in this way, so the behavior will be different fromfn3. At the same time, what is special aboutwriteln? Are you saying that you don't think one should be able to ever pass a function to another function? That would seem to miss one of the design goals of FCFs even if we have significant open questions in that area.