Open gavinking opened 9 years ago
My first reaction was “WTF, duck typing”?
But once you explained under which conditions it’s allowed… that actually seems reasonable, somewhat consistent with parameter type inference, and not unsound to me. Not sure if satisfies Duck
is so much of a nuisance to write that we should offer this, but I’m not opposed to it.
I guess this could also work nicely for dynamic interfaces, as an alternative to the dynamic [ … ]
syntax.
I might even be able to make this work for something like this:
Iterator<String> iterator() => object { next() => "hello"; };
Which would cut down on some verbosity in Iterable
.
This is basically analogous to the sort of logic we do to infer anonymous function parameter types.
Yes, I feel similarly. However I don't feel like this works here... I think it's one of those things were the compiler is being too clever and assuming too much stuff that would be better have explicit in the code... It could be a little confusing for people reading the code, even if it's the person who wrote it, reading it a couple minutes after they wrote it. Also the restriction makes it feel to me like it would be useful really rarely, and it would be one of those features that everyone forgets, and gets confused when they see it...
So how about the next step: allow to pass a function if the interface has only a single method?
So how about the next step: allow to pass a function if the interface has only a single method?
Well, right, that's where this idea came from—to be able to infer the type of a Java-style lambda.
I think it'd be better to map functional interfaces to Callable
or something like that...
@Zambonifofex Well there are some things we can perhaps do along those lines by they wouldn't be perfect. And this particular change is independently useful, IMO.
I like this option btw and extended to the Java-style lambda even more. I like the terseness better than any possible confusion it could cause for newbie users. (Doesn't mean we should always do it, but I don't feel we're stepping over the line here)
Oh, I was actually about to tell you that you could do this. Overloading complicates things a bit, but not too much.
So, in short, this is approved by math proofs.
On Sun, Nov 15, 2015 at 8:50 PM, Tako Schotanus notifications@github.com wrote:
* WARNING: THE ATTACHED DOCUMENT(S) CONTAIN MACROS \ MACROS MAY CONTAIN MALICIOUS CODE \ Open only if you can verify and trust the sender \ Please contact infosec@redhat.com if you have questions or concerns
I like this option btw and extended to the Java-style lambda even more. I like the terseness better than any possible confusion it could cause for newbie users. (Doesn't mean we should always do it, but I don't feel we're stepping over the line here)
— Reply to this email directly or view it on GitHub https://github.com/ceylon/ceylon/issues/5739#issuecomment-156888214.
Why just interfaces? You should be able to do this with any type at all (and then just error appropriately if you cannot inherit from it).
Well to inherit from a class you have to explicitly pass value arguments.
We could make it work for an intersection of interfaces.
We're getting more and more left-to-right inference, with its non-refactorability effects, and "invisible semantics". Where's the line? It'd also be nice to pass parameters lazily based on the receiver type, but IMO that hides behaviour from the caller, and can introduce really hairy issues with side-effects.
I mean, I do see what's nice about these callee-implied invisible-on-call-site behaviours, but I worry they stray from the original idea of "more clearly".
Interestingly enough, Snap! has lazy parameters. They look the same as eager ones from the caller. In fact, loops are literally just like functions in Snap!!
We're getting more and more left-to-right inference
Well, sure, and when we didn't have any left-to-right inference, I resisted adding it. But once we had it for anon function parameters, it made sense to have it for function refs. And now we've opened that door, I don't see that it makes much sense to rush to close it now.
with its non-refactorability effects
Well, its effect is to limit cut-and-paste. Tool based refactorings have no problem with these constructs.
Where's the line?
Well it's not like this is some totally open-ended thing where we'll keep adding more and more special cases of left-to-right inference. There's a rather limited set of cases where it is even possible in Ceylon. Our type system is simply not amenable to HM-style type inference, even if we wanted it.
And TBH I have absolutely no desire to add parameter type inference to anything other than anonymous functions.
I worry they stray from the original idea of "more clearly".
Well, I don't think that this particular feature makes the code less clear.
Actually it's possible that it opens up some interesting options for clean framework design that previously would not have bean feasible.
I would argue it's actually more clear. I mean the information we're leaving out does not hinder understanding the code much and might in many cases improve it by removing the non-essential.
And like using value
for example there might be times where you'd prefer to be more explicit just to make things more clear but most of the time value
work just fine. I see a similar situation here.
@quintesse I feel like the problem here is that there isn't anything indicating that there is implicit stuff going on...
@Zambonifofex sure, I just don't think it's the kind of implicitness that makes things difficult to understand, it's not like you have to go look all over the place to figure out what the heck the code is doing.
In the case of the object it's clear that something must be going on because passing an object without an interface to a function does make sense, so you look at the type of the parameter and you'll see the information you're looking for. It's more indirect perhaps but the information is right there.
In the case of passing a function to a single-method interface you might indeed not notice at first that the type of the parameter of the function it's passed to is not a function reference but an interface, but it does not take away from the understanding of the code when you read it IMO.
I feel like the problem here is that there isn't anything indicating that there is implicit stuff going on...
@Zambonifofex. I don't think that's quite right.
Remember, there is essentially no practical use at all for an object
expression with no supertypes. So if an object
expression has no explicit supertypes, one can infer pretty quickly that it must have implicit supertypes.
That, and the fact that it is explicitly refining methods that it doesn't itself declare!
So I don't see any real possibility for confusion here.
In the case of the object it's clear that something must be going on because passing an object without an interface to a function does make sense, so you look at the type of the parameter and you'll see the information you're looking for. It's more indirect perhaps but the information is right there.
Well it's even easier than that in fact. Just hover the object
expression in the IDE.
Just hover the object expression in the IDE.
I always think of the case of people looking at code in an article or a book, etc, it has to be understandable for them as well. :)
This is tantalizingly close to being really nice for Java 8 lambdas, but the {;}
and method name still get in the way.
This is tantalizingly close to being really nice for Java 8 lambdas, but the
{;}
and method name still get in the way.
Sure, but if we did this, I would tackle that problem next, as an abbreviation of this.
I always think of the case of people looking at code in an article or a book, etc, it has to be understandable for them as well. :)
IMO, the original method name (fun
in the example) provides plenty of context, given Ceylon (and Java) conventions.
Java 8 dispenses with interface names for this. And for something similar, Ceylon, unlike some languages, does not require (or even offer the ability for an API writer to require) parameter names, which are only used in named argument invocations. And, finally, you currently only have this "extra" argument type information when defining the object inline–it's missing if the argument is, for example, a top-level value.
I suppose this only works when the supertypes are omitted, and required. If you must pass a Duck
and you want to define Duck&Dog
then you must specify the supertypes, that is, Duck
should not be automagically inferred unless no supertypes are defined.
If this works AND is unambiguous, I have no problems whatsoever with this.
And to be sure, the extra object {...}
syntax does not bother me at all. If anything, it makes it so much clearer that we are defining inline anonymous object instance instead of an anonymous function. At the moment I like to make that distinction.
But I have next to no experience at the moment with interop with Java 8 API's from Ceylon, so it is a bit too early to form an opinion
+1. I like this also I find it nice and clear
I suppose this only works if you don't specify any supertypes, right? If you want your object to satisfy some other interface besides the inferred ones, you'll have to specify all supertypes?
If you want your object to satisfy some other interface besides the inferred ones, you'll have to specify all supertypes?
That's what I'm proposing, yes.
Are you planning on limiting this to interfaces with only one function? I guess supporting intersection types would not add much value if so..?
Are you planning on limiting this to interfaces with only one function?
No.
I guess supporting intersection types would not add much value if so..?
I plan to implement support for intersection types now.
It seems nobody objects strongly to this feature, so I'm going to merge this work to master now.
@gavinking Well, I do! I didn't say anything anymore because I didn't want to be redundant, but I strongly dislike this feature...
Another point I've been wanting to make: What if I have a function that accepts MyInterface
, and from one version of my module to another, I change the parameter type to Object
, or some other supertype of MyInterface
? Now other modules might be broken because of an apparently, and intuitively "innocent" change I made...
@Zambonifofex that is a reasonable objection. OTOH it's an objection that in principle also applies to parameter type inference. But perhaps it's worse in this case.
I'll give it some thought.
For me, it feels like this feature will be useful very rarely. It barely reduces boilerplate and increases clarity, besides being considerably intuitive...
I'm generally against this sort of inference. Honestly, I ain't even too keen on inference for lambda arguments' type, and type parameters inference; however they help eliminate a lot of boilerplate very frequently, and really help improve readability, unlike this feature, which I can't imagine being frequently used... As I said before, I think it will be one of those "WTF" features that people forget, and get confused when they see it...
While I have no objections to the feature itself, I'm not too fond of the 1.2.1 version target. Could we keep 1.2.x releases for bug fixes but not new features? That's how 1.1.1 became 1.2.0 and we had to wait one year for some bugs to be fixed in an official release.
Huh?! What the hell happened to @Zambonifofex? He's gone! And his comments with him...
I like this proposal in its original form.
I however, do not like the additional proposal of being able to pass an anonymous function or function reference if the required interface is one with only a single method.
That is unless a sort of named anonymous function or named function reference is used, of which the name must be the same as that of the single interface method being implemented. That, I feel, would make it clearer and more immediately intuitive, and therefore more at home in Ceylon.
This would also be awesome to have for TypeScript interop. When a function has a structural type as parameter type, e. g.
export function createServer(options?: { allowHalfOpen?: boolean; }, connectionListener?: (socket: Socket) =>void ): Server;
then all the TS model loader can do, I think, is to turn it into some toplevel type __StructuralType_1
. And it would be really ugly if users had to spell out that generated name.
A nice benefit of using this for implementing Java SAMs is that the possibly-null parameter and return type support would "just work".
For comparison, the best I could come up with when trying to write a flexible helper function consumer()
to create Consumer
s was:
Consumer<Arg> consumer<Arg>(Anything(Arg) | Anything(Arg?) f)
given Arg satisfies Object => object
satisfies Consumer<Arg> {
accept = maybeFun(f);
};
Result(Arg?) maybeFun<Result, Arg>(Result(Arg?) | Result(Arg) f)
=> let(nullFunction
= if (is Anything(Arg?) f) then f else null)
((Arg? t) {
if (exists nullFunction) {
return nullFunction(t);
}
if (!exists t) {
throw NullPointerException();
}
return f(t);
});
Consider the following code:
I just did a quick and rough implementation of inference for
object
expression interfaces such that the following code passes the typechecker and nothing else important seems to have broken:The logic is:
object
expression has no explicit superclass nor super-interfaces, andthen the supertype is inferred to be the type of the function parameter.
This is basically analogous to the sort of logic we do to infer anonymous function parameter types. I like it.
I'm still not sure there aren't any holes in the typechecker logic, however.
Reactions?