Cheeser had an offhad comment on the IRC about the zillion of call methods for each arity in sys::Method and sys::Func. And it dawned on me that is really a relic of the language design before I had done default parameters.
So I propose this breaking change...
Change Func and Method so that all the callX methods get collapsed into one call method:
Obj? call(Obj? a := null, Obj? b := null ...)
I support that pattern up to eight arguments. Under the covers I would basically use the same code I already have, so efficiency would be the same.
That means the existing call(List) method needs to get renamed into something. I'd suggest renaming it callList (although I am open to suggestions).
Yeah or nay on that change?
tacticsMon 11 May 2009
There is slight weirdness where you don't have to explicitly pass null parameters (you can call a |Obj?->Obj| with .call(), not .call(null)), but in practice, I don't think it comes up often enough to matter. This sounds like a good change.
brianMon 11 May 2009
There is slight weirdness where you don't have to explicitly pass null parameters (you can call a |Obj?->Obj| with .call(), not .call(null)), but in practice, I don't think it comes up often enough to matter. This sounds like a good change.
Yeah kind of - it is just reflection basically. If you don't pass the right number of params (or you pass invalid types), then you get a runtime error. So I don't see a big difference between calling the wrong version of callX versus call(null,...).
Although it is weird in that the semantics aren't exactly the same for the various arities: call(null) is not the same semanatics are call(null, null) even though the second param has a default value of null.
tacticsMon 11 May 2009
Where you going to ax the callX's in this change?
brianMon 11 May 2009
Where you going to ax the callX's in this change?
Yes, assuming we replace them all with one call method
tompalmerMon 11 May 2009
I like the change, except that I'm concerned about compiler magic. Well, if the behavior is expected but just happens to be efficiently executed, I won't complain.
Also, somehow I'd missed that Fan supports varargs.
Side note, Scala even has a new class (or is it trait?) for each arity up to some high number. If I remember it correctly. Bloat like that scares me. It strikes me as something having been wrongly designed.
qualidafialMon 11 May 2009
Also, somehow I'd missed that Fan supports varargs.
I've read all the docs and not seen anything about this.
andyMon 11 May 2009
Also, somehow I'd missed that Fan supports varargs.
Fan does not support varargs.
tompalmerMon 11 May 2009
Fan does not support varargs.
So, this is a special case? If so, I personally don't vote in favor of this change.
Maybe do this instead?
Obj? call(Obj? a := null, Obj[]? b := null)
Still, if the compiler sees an array literal in place there, it could automatically optimize the array creation away. Behavior would be the same, right? I guess, unless you put in AOP-ish hooks (even such as breakpoints) on array creation. It would also need commented or else the potential optimization (using literals vs. variables or other expressions) would be a dark secret.
Hmm.
qualidafialMon 11 May 2009
Fan does not support varargs.
Varargs seem redundant when you already have a light-weight list syntax.
brianMon 11 May 2009
So, this is a special case? If so, I personally don't vote in favor of this change.
This proposal has nothing to do with var-args.
This proposal is based on using default parameters to replace the callX methods:
call(Obj? a := null, Obj? b := null, Obj? c := null, Obj? d := null ...)
callList(Obj?[]? args)
// today => proposed
call0() => call()
call1(a) => call(a)
call2(a,b) => call(a, b)
call3(a,b,c) => call(a, b, c)
...
call(args) => callList(args)
The only weird side effect is that all methods with default parameters basically route to the version with all the parameters. In this case that wouldn't be true since these are all optimizations for the different arities.
tompalmerMon 11 May 2009
Okay. Sorry for being slow. I'm willing to vote for that.
Currying won't optimize, then, I presume? I'd be fine with that, too.
brianWed 13 May 2009
Promoted to ticket #592 and assigned to brian
brianWed 13 May 2009
Ticket resolved in 1.0.42
This change effects bootstrap build off the tip. If you are working off tip, you will need to "bootstrap thru" changeset d2315dd82771. Although I will be doing a full build later today, so I'd suggest just waiting until I have the new build posted.
tompalmerFri 22 May 2009
I was just thinking about this. Seems like if you specified only some of the args, the others defaulting to null would try to pass nulls in. And that seems much different from passing nothing at all (say if the method itself had optional args defaulting to other values or whatnot).
In other words, I'm not sure if the "under the covers" implementation is legit. Or do I misunderstand something?
tompalmerFri 22 May 2009
Maybe my question relates to this prior comment:
The only weird side effect is that all methods with default parameters basically route to the version with all the parameters. In this case that wouldn't be true since these are all optimizations for the different arities.
Not 100% sure, though.
tacticsFri 22 May 2009
tompalmer: Are you talking about a case where you have a non-null default value in a method?
Void foo(Int? value := -1)
{
// whatever
}
// later on...
foometh.call // this calls foo(null) instead of the desired foo(-1)
brianFri 22 May 2009
I was just thinking about this. Seems like if you specified only some of the args, the others defaulting to null would try to pass nulls in. And that seems much different from passing nothing at all (say if the method itself had optional args defaulting to other values or whatnot).
The signature defaults params to null, but what really happens on the under covers is that those methods are handled specially. So x.call(a) is not necessarly the same thing as x.call(a, null). In the first case if two parameters were required you would get an error. So the semantics aren't perfectly consistently with default parameters, but I think this better than the ugliness we had before.
JohnDGFri 22 May 2009
So the semantics aren't perfectly consistently with default parameters
To deal with this, you'd need something like JavaScript's undefined -- a separate beast from null. Sometimes I find myself wishing for two magical values in Java, as there are cases when null has a perfectly good meaning which is distinct from "not present" or "not specified".
tacticsFri 22 May 2009
The problem here is there is no such thing as a "double nullable" field in Fan. In Haskell or ML, you would model this problem with the Maybe data type. Then, when the method itself also takes a Maybe type as a parameter, the resultant type is Maybe (Maybe int) or whatever. The legitimate values for this are Nothing, indicating no parameter was passed, Just Nothing, indicating "'Nothing'" itself was passed, and Just (Just value), indicating a non-null parameter value.
Not that Fan has any need at all for that kind of generality for such a minor issue, but that's the route cause of ambiguity here: there is no such thing in Fan as an Obj??. Your idea of an undefined keyword matches pretty closely with the notion of a hypothetical Obj??. It would map to the example above like this:
undefined => Nothing
null => Just Nothing
value => Just (Just value)
But, like I said, it's a minor case. In the eyes of type theorists, Fan's type system is messed up already, and from a practical standpoint, how often is distinguishing between Maybe Int and Maybe (Maybe Int) ever really going to be useful?
JohnDGFri 22 May 2009
You also want it for builders to distinguish between the user not setting a value (thus leaving it default), and the user setting a value to null. The problem with your analogy between Maybe (Maybe Int) and Int?? is that there'd be no way to check for such a thing, because the state of Int being null is indistinguishable from the state of Int? being null. In Haskell you have pattern matching to distinguish between them.
tacticsSat 23 May 2009
That Fan can't distinguish between the two null forms was the point of the analogy. If there was such a thing as an undefined value, the analogy would fit. But, it seems like a rare enough case Fan can do without it.
tompalmerSat 23 May 2009
But, it seems like a rare enough case Fan can do without it.
Agreed that it would be too much bother/complexity. I was just a bit sad when I finally thought enough about the change made to the API here to realize that some magic was introduced to break standard semantics.
brianTue 26 May 2009
Yeah, undefined would be perfect for this problem since it would clearly have another meaning than null (unless of course undefined was itself potentially an expected parameter). I've actually run across a couple other use cases where undefined would make sense, but certainly not enough to warrant adding it and its associated complexity to the language (which would be really hard since neither the JVM or CLR implicitly supports such a beast like they do null).
So, just to make sure everyone is aware - I have already implemented this feature as originally described.
tompalmerFri 12 Jun 2009
Sorry to come back to this, but it occurred to me that you could get a smaller API and avoid breaking the rules of the language if you used a slightly different technique.
You could just change callList back to call and leave out any other variants. Then you could optimize at the compiler level for in-place list literals. No one else would ever see the list, so it wouldn't matter if you made it disappear, right?
Example:
func.call([1, 2, "Hello"])
would do the same as
func.call(1, 2, "Hello")
does today. However, you just wouldn't bother optimizing at the compiler for any other case. Even things as simple as
args := [1, 2, "Hello"]
func.call(args)
could be left unoptimized. I think the rule could even be documented (though affecting only some platforms) so that people know they are allowed to take advantage of it.
Just want to throw it out there, since I prefer against making exceptions to language semantics.
brianFri 12 Jun 2009
That would be ambiguous if the first argument was itself a list.
tompalmerFri 12 Jun 2009
That would be ambiguous if the first argument was itself a list.
No. It would always take a list. To pass the first argument as a list, you'd have to say like so:
func.call([[2, 3], "arg2"])
In other words, it would have the exact same semantics as the current callList. You'd just have a compiler optimization if the list was provided directly in place.
JohnDGFri 12 Jun 2009
I do want varargs support to address these kinds of issues (and others).
Varargs is just a matter of syntax. The core idea that I'm proposing would be the same, either way.
I'm mixed on varargs. It seems a clearer language to avoid it (so maybe I'm most in favor of the lack of varargs), but even those two chars can be noisy-looking sometimes.
This issue (of how many args and how to optimize that) shows up in my Mat indexing, by the way. Right now, you have to say mat[[1, 2, 3]] instead of mat[1, 2, 3], and optimized forms will probably need to look like mat.getf3(1, 2, 3), but I'm mulling over the options.
brianSat 13 Jun 2009
I think you are suggesting that I have to manually box everything into a list and the [...] syntax would be required? If so I don't really like that, just noise. I think the use case here is the call with individual args is the 95% case and using call with a list is the 5% case - that is why I changed it to begin with to use the "good" name for the common case.
John is right that this is basically a var args problem (and I've hacked a special case for functions).
tompalmerSat 13 Jun 2009
John is right that this is basically a var args problem (and I've hacked a special case for functions).
It really doesn't need to be, but I guess I'll survive it.
brian Mon 11 May 2009
Cheeser had an offhad comment on the IRC about the zillion of call methods for each arity in
sys::Method
andsys::Func
. And it dawned on me that is really a relic of the language design before I had done default parameters.So I propose this breaking change...
Change Func and Method so that all the callX methods get collapsed into one call method:
I support that pattern up to eight arguments. Under the covers I would basically use the same code I already have, so efficiency would be the same.
That means the existing
call(List)
method needs to get renamed into something. I'd suggest renaming itcallList
(although I am open to suggestions).Yeah or nay on that change?
tactics Mon 11 May 2009
There is slight weirdness where you don't have to explicitly pass null parameters (you can call a
|Obj?->Obj|
with.call()
, not.call(null)
), but in practice, I don't think it comes up often enough to matter. This sounds like a good change.brian Mon 11 May 2009
Yeah kind of - it is just reflection basically. If you don't pass the right number of params (or you pass invalid types), then you get a runtime error. So I don't see a big difference between calling the wrong version of
callX
versuscall(null,...)
.Although it is weird in that the semantics aren't exactly the same for the various arities:
call(null)
is not the same semanatics arecall(null, null)
even though the second param has a default value ofnull
.tactics Mon 11 May 2009
Where you going to ax the callX's in this change?
brian Mon 11 May 2009
Yes, assuming we replace them all with one call method
tompalmer Mon 11 May 2009
I like the change, except that I'm concerned about compiler magic. Well, if the behavior is expected but just happens to be efficiently executed, I won't complain.
Also, somehow I'd missed that Fan supports varargs.
Side note, Scala even has a new class (or is it trait?) for each arity up to some high number. If I remember it correctly. Bloat like that scares me. It strikes me as something having been wrongly designed.
qualidafial Mon 11 May 2009
I've read all the docs and not seen anything about this.
andy Mon 11 May 2009
Fan does not support varargs.
tompalmer Mon 11 May 2009
So, this is a special case? If so, I personally don't vote in favor of this change.
Maybe do this instead?
Still, if the compiler sees an array literal in place there, it could automatically optimize the array creation away. Behavior would be the same, right? I guess, unless you put in AOP-ish hooks (even such as breakpoints) on array creation. It would also need commented or else the potential optimization (using literals vs. variables or other expressions) would be a dark secret.
Hmm.
qualidafial Mon 11 May 2009
Varargs seem redundant when you already have a light-weight list syntax.
brian Mon 11 May 2009
This proposal has nothing to do with var-args.
This proposal is based on using default parameters to replace the callX methods:
The only weird side effect is that all methods with default parameters basically route to the version with all the parameters. In this case that wouldn't be true since these are all optimizations for the different arities.
tompalmer Mon 11 May 2009
Okay. Sorry for being slow. I'm willing to vote for that.
Currying won't optimize, then, I presume? I'd be fine with that, too.
brian Wed 13 May 2009
Promoted to ticket #592 and assigned to brian
brian Wed 13 May 2009
Ticket resolved in 1.0.42
This change effects bootstrap build off the tip. If you are working off tip, you will need to "bootstrap thru" changeset d2315dd82771. Although I will be doing a full build later today, so I'd suggest just waiting until I have the new build posted.
tompalmer Fri 22 May 2009
I was just thinking about this. Seems like if you specified only some of the args, the others defaulting to null would try to pass nulls in. And that seems much different from passing nothing at all (say if the method itself had optional args defaulting to other values or whatnot).
In other words, I'm not sure if the "under the covers" implementation is legit. Or do I misunderstand something?
tompalmer Fri 22 May 2009
Maybe my question relates to this prior comment:
Not 100% sure, though.
tactics Fri 22 May 2009
tompalmer: Are you talking about a case where you have a non-null default value in a method?
brian Fri 22 May 2009
The signature defaults params to null, but what really happens on the under covers is that those methods are handled specially. So
x.call(a)
is not necessarly the same thing asx.call(a, null)
. In the first case if two parameters were required you would get an error. So the semantics aren't perfectly consistently with default parameters, but I think this better than the ugliness we had before.JohnDG Fri 22 May 2009
To deal with this, you'd need something like JavaScript's
undefined
-- a separate beast fromnull
. Sometimes I find myself wishing for two magical values in Java, as there are cases whennull
has a perfectly good meaning which is distinct from "not present" or "not specified".tactics Fri 22 May 2009
The problem here is there is no such thing as a "double nullable" field in Fan. In Haskell or ML, you would model this problem with the
Maybe
data type. Then, when the method itself also takes aMaybe
type as a parameter, the resultant type isMaybe (Maybe int)
or whatever. The legitimate values for this areNothing
, indicating no parameter was passed,Just Nothing
, indicating "'Nothing'" itself was passed, andJust (Just value)
, indicating a non-null parameter value.Not that Fan has any need at all for that kind of generality for such a minor issue, but that's the route cause of ambiguity here: there is no such thing in Fan as an
Obj??
. Your idea of anundefined
keyword matches pretty closely with the notion of a hypotheticalObj??
. It would map to the example above like this:But, like I said, it's a minor case. In the eyes of type theorists, Fan's type system is messed up already, and from a practical standpoint, how often is distinguishing between
Maybe Int
andMaybe (Maybe Int)
ever really going to be useful?JohnDG Fri 22 May 2009
You also want it for builders to distinguish between the user not setting a value (thus leaving it default), and the user setting a value to
null
. The problem with your analogy betweenMaybe (Maybe Int)
andInt??
is that there'd be no way to check for such a thing, because the state ofInt
beingnull
is indistinguishable from the state ofInt?
beingnull
. In Haskell you have pattern matching to distinguish between them.tactics Sat 23 May 2009
That Fan can't distinguish between the two
null
forms was the point of the analogy. If there was such a thing as anundefined
value, the analogy would fit. But, it seems like a rare enough case Fan can do without it.tompalmer Sat 23 May 2009
Agreed that it would be too much bother/complexity. I was just a bit sad when I finally thought enough about the change made to the API here to realize that some magic was introduced to break standard semantics.
brian Tue 26 May 2009
Yeah, undefined would be perfect for this problem since it would clearly have another meaning than null (unless of course undefined was itself potentially an expected parameter). I've actually run across a couple other use cases where undefined would make sense, but certainly not enough to warrant adding it and its associated complexity to the language (which would be really hard since neither the JVM or CLR implicitly supports such a beast like they do null).
So, just to make sure everyone is aware - I have already implemented this feature as originally described.
tompalmer Fri 12 Jun 2009
Sorry to come back to this, but it occurred to me that you could get a smaller API and avoid breaking the rules of the language if you used a slightly different technique.
You could just change
callList
back tocall
and leave out any other variants. Then you could optimize at the compiler level for in-place list literals. No one else would ever see the list, so it wouldn't matter if you made it disappear, right?Example:
would do the same as
does today. However, you just wouldn't bother optimizing at the compiler for any other case. Even things as simple as
could be left unoptimized. I think the rule could even be documented (though affecting only some platforms) so that people know they are allowed to take advantage of it.
Just want to throw it out there, since I prefer against making exceptions to language semantics.
brian Fri 12 Jun 2009
That would be ambiguous if the first argument was itself a list.
tompalmer Fri 12 Jun 2009
No. It would always take a list. To pass the first argument as a list, you'd have to say like so:
In other words, it would have the exact same semantics as the current
callList
. You'd just have a compiler optimization if the list was provided directly in place.JohnDG Fri 12 Jun 2009
I do want varargs support to address these kinds of issues (and others).
tompalmer Fri 12 Jun 2009
Varargs is just a matter of syntax. The core idea that I'm proposing would be the same, either way.
I'm mixed on varargs. It seems a clearer language to avoid it (so maybe I'm most in favor of the lack of varargs), but even those two chars can be noisy-looking sometimes.
This issue (of how many args and how to optimize that) shows up in my
Mat
indexing, by the way. Right now, you have to saymat[[1, 2, 3]]
instead ofmat[1, 2, 3]
, and optimized forms will probably need to look likemat.getf3(1, 2, 3)
, but I'm mulling over the options.brian Sat 13 Jun 2009
I think you are suggesting that I have to manually box everything into a list and the
[...]
syntax would be required? If so I don't really like that, just noise. I think the use case here is the call with individual args is the 95% case and using call with a list is the 5% case - that is why I changed it to begin with to use the "good" name for the common case.John is right that this is basically a var args problem (and I've hacked a special case for functions).
tompalmer Sat 13 Jun 2009
It really doesn't need to be, but I guess I'll survive it.