#592 Proposal: Method/Func call

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 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?

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

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.

tactics Mon 11 May 2009

Where you going to ax the callX's in this change?

brian Mon 11 May 2009

Where you going to ax the callX's in this change?

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

Also, somehow I'd missed that Fan supports varargs.

I've read all the docs and not seen anything about this.

andy Mon 11 May 2009

Also, somehow I'd missed that Fan supports varargs.

Fan does not support varargs.

tompalmer Mon 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.

qualidafial Mon 11 May 2009

Fan does not support varargs.

Varargs seem redundant when you already have a light-weight list syntax.

brian Mon 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.

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:

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.

tactics Fri 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)

brian 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).

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.

JohnDG Fri 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".

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 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?

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 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.

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 an undefined value, the analogy would fit. But, it seems like a rare enough case Fan can do without it.

tompalmer Sat 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.

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 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.

brian Fri 12 Jun 2009

That would be ambiguous if the first argument was itself a list.

tompalmer Fri 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.

JohnDG Fri 12 Jun 2009

I do want varargs support to address these kinds of issues (and others).

Void foo(Obj... arguments) {
}

foo(a)
foo(a, b)
foo(a, b, c)

=============>>>>>>>>>>

Void foo(Obj[] arguments) {
}

foo([a])
foo([a, b])
foo([a, b, c])

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 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.

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

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.

Login or Signup to reply.