eclipse-archived / ceylon

The Ceylon compiler, language module, and command line tools
http://ceylon-lang.org
Apache License 2.0
399 stars 62 forks source link

support ? and [] in positional parameter lists #3510

Open CeylonMigrationBot opened 12 years ago

CeylonMigrationBot commented 12 years ago

[@gavinking] Given

Integer f(Integer x, Integer y) { return x*y; }

Support the syntax:

f(x, y?)

meaning, approximately:

exists y then f(x,y)

and

f(x, ys[])

meaning, approximately:

{ for (y in ys) f(x,y) }

[Migrated from ceylon/ceylon-spec#404]

CeylonMigrationBot commented 12 years ago

[@RossTate] To prevent ambiguity, you'd have to make sure only one argument uses [].

CeylonMigrationBot commented 12 years ago

[@gavinking] > To prevent ambiguity, you'd have to make sure only one argument uses [].

Interesting. I guess you're right.

CeylonMigrationBot commented 12 years ago

[@gavinking] P.S. A quick note: I intend for this to have the "modest" interpretation of affecting no more than the invocation expression itself. @RossTate has proposed that postfix ? could be a totally general-purpose operator which would affect the entire containing expression. I believe that this "ambitious" interpretation is something we could adopt later in a more-or-less backward compatible way. For now, I think the "modest" interpretation is sufficient.

FTR, the difference between the two interpretations is that Ross would let you write:

Integer? z = f(x, y?) + 1;

Whereas I would not, or at least, not yet. I would force you to write:

Integer? z = 1.plus(f(x, y?)?);
CeylonMigrationBot commented 12 years ago

[@gavinking] > For now, I think the "modest" interpretation is sufficient.

Actually, upon reflection, I think I might be wrong. I think it's very nearly as easy to implement @RossTate's approach, and significantly more useful.

The only question to decide is how does what Ross calls "capture" work? If it's an implicit thing then:

Now, it would also be super-convenient for

So you could write:

Integer z = f(x, y?) + 1 else -1;

And:

if (exists x = process.arguments[name?]) { ... }
if (nonempty names = people[].name) { ... }
for (name in people[].name) { ... }

But how can we define that behavior in a non-totally-arbitrary way, with no surprising corner cases? Could we say that capture occurs whenever the expression appears in a location that accepts an optional/iterable type? So therefore:

Or are there surprising corner cases in this? If so, should "capture" be an explicit thing? Then what would the syntax be?

CeylonMigrationBot commented 12 years ago

[@gavinking] > If so, should "capture" be an explicit thing? Then what would the syntax be?

Ross and I have been discussing this on IM. He suggests:

So you would write:

Integer z = f(x, y?) + 1 else -1;
for (name in {people[].name}) { ... }
if (exists x = process.arguments[name?] else null) { ... }

And you could even interpret:

{ f(xs[], y?) }

To mean:

{ for (x in xs) if (exists y) f(x,y) }

I personally find it a bit strange to write f(x?) else null because it looks like a no-op—you're writing in the else null just purely for the side-effect of else.

CeylonMigrationBot commented 12 years ago

[@FroMage] I find this feature extremely confusing, due to the number of rules required to understand what the hell it does, and due to the fact that inner expressions affect outer ones.

I vote we don't do that until people request that sort of thing. I've personally never wanted something like that.

CeylonMigrationBot commented 12 years ago

[@quintesse] I agree, we're drifting more and more away from a language that Java developers will easily understand and feel at home with (IMO of course), let's not just do things because we can. If before we talked about complexity budgets I think we should only look at what it means to implement it but also what it means to understand it.

CeylonMigrationBot commented 12 years ago

[@tombentley] I really want to like it, but I agree with @FroMage: the way it affects outer expressions is just completely alien to me. All of a sudden I can't rely on what appears to be an invocation actually being a normal invocation: I have to inspect all N arguments to find out.

CeylonMigrationBot commented 12 years ago

[@gavinking] Look, I'm pretty irritated with your guys reaction to this, because both @quintesse and @FroMage have been the source of numerous complaints about ergonomics which would be almost completely solved by the ? operator. It's very hard for me to take your complaints seriously if you won't take potential solutions seriously instead of reacting emotionally and knocking (rough, underdeveloped) ideas on the head before we have had time to develop them properly. Beautiful, elegant, powerful language features don't suddenly spring out fully-formed in one day!

Right now we're exploring what this feature could look like, how it could fit into the language, and how powerful it could be before it would be too unpredictable and too difficult to understand. Obviously I agree that if we can't find a way to make the "ambitious" interpretation proposed by Ross predictable easy to understand then we should do something more "modest". That's why the initial description at the top of this issue is of something extremely simple, useful, and easy to understand. Nevertheless, we would be totally crazy to not take Ross's ideas seriously and see if they can be made to work. What Ross has proposed is something extremely powerful that could dramatically simplify quite a lot of code. Now, after thinking about this quite a bit last night, I'm very skeptical that it can be made to work out, but if we could make it work, that would be just too awesome. So not even trying would be frankly idiotic.

So instead of jumping on me for posting my speculations here, in the hope that other people might have some helpful thinking to contribute, perhaps you could, like me, suspend disbelief for just a few minutes to take this seriously and think it through properly.

CeylonMigrationBot commented 12 years ago

[@FroMage] Please credit us with thinking about this no less properly than you ;)

As it is, I find it very confusing. That's my opinion. Maybe you don't want it, but it still exists. Now, if you refine it to something that I will like, I will tell you, like I've done in the past, so don't accuse me of having knee-jerk reactions, I've given in to a lot of things I wanted because you were able to convince me otherwise.

I honestly don't see how any of the complaints I made could be solved by this, but even if they were, I still find that sort of feature confusing (both as it is listed in the issue description and in the more powerful version).

I can also stop giving my opinion on things if you think it's worthless, but I hope not. What I'm telling you is that I'm sure you can make something like that work with some work, but as it is stated here, that doesn't work for me.

CeylonMigrationBot commented 12 years ago

[@gavinking] > I still find that sort of feature confusing (both as it is listed in the issue description and in the more powerful version).

Hold on. So you're telling me you find this confusing:

String? name = trim(text?);

Even though you quote "love" the following:

String? name = text?.trim();

Sorry, Stef, but I simply don't believe you. I don't believe that you're confused by the first code example. I think you understood it immediately. More to the point, I don't see how it is possible that a person could find the second code example reasonable, but the first unreasonable.

CeylonMigrationBot commented 12 years ago

[@quintesse] Well you might think it impossible, but the second has a "sequentiality" to it that the first lacks. Again, if we're targeting Java-esque developers with this it is really easy to reason about text?.trim() , "you start with a text object and if it isn't null you call trim() on it". The second example seems more like "calling trim() with something that could be a text... oh no maybe we're not calling trim() at all!". And what about foo().bar.trim(text?)? Does foo() get called if text is null, does the getter for bar get called? Does trim()? Of course we can come up with the rules for those, but I don't find it at all obvious.

CeylonMigrationBot commented 12 years ago

[@FroMage] > So you're telling me you find this confusing: String? name = trim(text?);

Yes. It's not left-to-right, evaluation order rules are not evident, or defined by my background: I find it extremely confusing. I can believe that you find it intuitive and evident, but I don't.

text?.trim() on the other hand is immediately evident from having done a bit of Groovy, but when I discovered it in Groovy it felt pretty much evident at the time.

CeylonMigrationBot commented 12 years ago

[@gavinking] Guys, that's about the lamest working-backwards-self-justification I've ever seen. I know you know as well as I do that there is nothing very special about the OO-style postfix notation—text is just a parameter of trim() whether we write it prefix with a . or postfix inside (). Therefore I continue to disbelieve you.

Yeah, sure, I believe that text?.trim() was more familiar because you had seen it before in Groovy, but trying to claim it's more understandable is simply absurd.

My hypothesis that you're reacting emotionally has simply much more explanatory value than the alternative explanation.

CeylonMigrationBot commented 12 years ago

[@quintesse] Sure, let's just dismiss the opinions of several of your colleagues because your self-serving explanation is so much easier to live with ;)

CeylonMigrationBot commented 12 years ago

[@gavinking] > Sure, let's just dismiss the opinions of several of your colleagues because your self-serving explanation is so much easier to live with ;)

Exactly. When you're asking me to believe that you don't understand what this means:

String? name = trim(text?);

Then there are two possible explanations:

  1. you're emotionally rat-holing, or
  2. you're stupid.

Principle of charity states that when in doubt always assume the most charitable possible interpretation and motivations on the part of your interlocutor. That would be option 1—which is also the objectively more probable explanation in light of the fact that I already know you're not stupid. And, as you say, this is also the explanation that is much easier for me to live with.

QED.

CeylonMigrationBot commented 12 years ago

[@quintesse] Ok, Gavin has gone back to complete ass-hole mode again, everybody hide!

CeylonMigrationBot commented 12 years ago

[@FroMage] > My hypothesis that you're reacting emotionally has simply much more explanatory value than the alternative explanation.

Gavin, you can twist the truth to make it fit inside your own personal world view, but that doesn't make it true.

It is more understandable to me because I understood it. Now you can call me stupid, but you can't call me a liar.

The fact that you claim that we are reacting emotionally here is a good sign that you should start reassessing your personal opinion on the problem and the following discussion because we told you what we thought, and gave perfectly valid reasons for thinking that and you reacted quite violently and frankly absurdly by claiming out of thin air that we're absurd and emotional.

So I don't know why you're so worked up about the fact that we find this sort of feature confusing, but please rather than saying that we're something or other for thinking it, admit that you don't care what our opinion on this topic is because that's what's really happening here.

CeylonMigrationBot commented 12 years ago

[@gavinking] > So I don't know why you're so worked up about the fact that we find this sort of feature confusing, but please rather than saying that we're something or other for thinking it, admit that you don't care what our opinion on this topic is because that's what's really happening here.

The problem is that I do care what your opinion is. And that I take your opinion into account all the fucking time. Which imposes on you the obligation to give me your thoughtful opinion, not your kneejerk.

CeylonMigrationBot commented 12 years ago

[@FroMage] But that was my thoughtful opinion, and motivated. I can motivate it more, but you said it was still evolving so I thought I should wait for it to evolve more to see if it became less confusing to me.

My biggest problems with foo(a?) is that it's not left-to-right, its pretty unique so I can't guess from experience what it could be, it makes outer expressions evaluations depend on inner expressions, and my brain told me there was a default value missing between ? and ).

I have a similar problem with Perl's foo(a) unless a, I find it confusing.

CeylonMigrationBot commented 12 years ago

[@maxandersen] In case its relevant my brain reads:

String? name = trim(text?);

as

call trim() or trim(text) wether text is null or not.

I really really need to twist my brain to see it as a call against text.trim(); and when I do I feel very uncertain when to apply that rule; i.e. what happens if the context I call this in already have a trim() method available ?

If the question is about readability then text?.trim() is much clearer to me since it can be naturally nested too (x?.y()?.trim()) where as the alternate seems weird:

trim(y(x?)?)

is that the correct alternative ? where its now the result of the "inner" that affects what is called in the "outer" case ?

note, I can see the "cleanliness" of this, but damn its hard to read. (same reason why I understand math but still hate reading or writing it ;)

CeylonMigrationBot commented 12 years ago

[@quintesse] You mean thoughtful like this ceylon/ceylon-ide-eclipse#381#issuecomment-8332823 ?

Anyway, I don't know how to explain it to you in any other way. I look at this @RossTate 's suggestion and I see the potential and I think it's a neat idea, but I also think it's not obvious at all and I also think that lot's of people will have problems with it. Now, do I have any proof for this? No of course not, we'll only be able to see that when people start using it, that's why it's called my opinion. But an opinion formed by years of experience working with people for whom programming is nothing more than a job, not the love of their life. Now I'm not saying I'm "definitely against" this, but it will need more convincing examples than { f(xs[], y?) } which could almost as easily but in my opinion at least be much much more clearly written as { for (x in xs) if (exists y) f(x,y) }

CeylonMigrationBot commented 12 years ago

[@FroMage] > [I read it as] call trim() or trim(text) wether text is null or not.

Actually that is an interpretation of the code that I can understand, even though I'm not sure how useful that would be.

CeylonMigrationBot commented 12 years ago

[@gavinking] > Now I'm not saying I'm "definitely against" this, but it will need more convincing examples than { f(xs[], y?) } which could almost as easily but in my opinion at least be much much more clearly written as { for (x in xs) if (exists y) f(x,y) }.

Right, and this is exactly the point. When working with iterables, comprehensions already give us a way to do the kind of thing you would do with []. But we have nothing equivalent to ? for working with null.

CeylonMigrationBot commented 12 years ago

[@gavinking] > I really really need to twist my brain to see it as a call against text.trim(); and when I do I feel very uncertain when to apply that rule; i.e. what happens if the context I call this in already have a trim() method available ?

Max, FTR, what is being proposed here is not to allow you to call the trim() method via a postfix notation. In trim(text?) I'm assuming that trim() is a toplevel function, and in text?.trim() I'm assuming that trim() is a method of String.

CeylonMigrationBot commented 12 years ago

[@maxandersen] oh wait...so i'm misunderstanding the scope of this change.

Would both of these be valid in this suggestion:

String? name = trim(text?);

String? name = text?.trim();

Making this a question about extending the notion of var? ("do something but only if var exist") to also apply to arguments on method calls ?

(now that I read the title I believe so, but just want to be sure I comprehend ;)

If that is the case I do view it in a different light but I must say i'm still puzzled about #3510#issuecomment-8412596

Integer z = f(x, y?) + 1 else -1;

Okey - so now ? also applies to parameters - hard to read for sure, but my assumption would be this is equal to (in java):

Integer z = (y!=null? f(x,y) +1:-1)

correct ?

In this case I like java better - it reads left from right.

But where the suggested syntax is nice is that I assume it applies to multiple arguments too, so this would be fine too:

Integer z = f(x,y?,q?,r?) + 1 else -1

Correct ? Here the suggestion definitely wins for compactness and ? on its own becomes this new kind of operator. Special for sure, but I can grok it.

for (name in {people[].name}) { ... }

So this means iterate all people if people is not null ? ... for some reason I would like that better if it looked like:

for (name in {people[]?.name}) { ... }

but now its starting to look like a character soup.

if (exists x = process.arguments[name?] else null) { ... }

This reads fine when I have accepted var? as applying to parameters.

{ f(xs[], y?) }

My mind still can't read as an sequence that iterates xs[] only when y exists though.

CeylonMigrationBot commented 12 years ago

[@gavinking] >> Integer z = (y!=null? f(x,y) +1:-1)

In this case I like java better - it reads left from right.

Right, but you can't write:

Integer z = exists y then f(x,y)+1 else -1;

in Ceylon, because Ceylon has typesafe nulls. Therefore, we need something more sophisticated. Yes, it would probably be possible to have an exists operator that acts to change the type of y in the middle of an expression. And yeah, it would be possible to analyze all the || and && branches to do the right reasoning when you have multiple null things but, wow, that's a much more complicated thing to implement and the only thing gained from all that work is extra verbosity.

But where the suggested syntax is nice is that I assume it applies to multiple arguments too

Right, it "coalesces" the nulls, which is the whole point of this stuff. (I know you don't care, but the reason why this works is that Option is a monad.)

So this means iterate all people if people is not null

No, it just means iterate all the people. If people is null, it's a type error.

{ f(xs[], y?) }

My mind still can't read as an sequence that iterates xs[] only when y exists though.

It's not completely clear to me that combining [] and ? in the same expression is OK—actually, it might be ambiguous. So we might need to prevent that anyway.

CeylonMigrationBot commented 12 years ago

[@RossTate] I propose we leave [] aside for now and just focus on ?.

In another thread, the issue was raised that making null explicit imposes a significant burden on the programmer, especially given some of our designs. For example, what can be written as sum = a[0] + a[1] + a[2] + a[3] I think right now has to be written as

Integer? sum;
if (exists a0 = a[0]) {
  if (exists a1 = a[1]) {
    if (exists a2 = a[2]) {
      if (exists a3 = a[3]) {
        sum = a0 + a1 + a2 + a3;
      } else { sum = null; }
    } else { sum = null; }
  } else { sum = null; }
} else { sum = null; }

That's a pretty big pain in the ass. At best we can simplify this to

Integer? sum;
if (exists a0 = a[0] && exists a1 = a[1] && exists a2 = a[2] && exists a3 = a[3]) {
  sum = a0 + a1 + a2 + a3;
} else {
  sum = null;
}

That's better, but still pretty dissatisfying. So I'm trying to find something that'll lighten this burden. I think there really needs to be some such something. This is my best guess right now. With this proposal we'd just have to write

var sum = a[0]? + a[1]? + a[2]? + a[3]? else null;

and maybe even drop the else null. Now, I'm used to these lifting notions, so this reads fine to me, but people raise good points about evaluation and such. Maybe an intuition is that ? essentially means "throw NullPointerException if null" and else essentially means "catch NullPointerExcpetion". This way nulls are still explicit, but you don't have to manually propagate them through every step of a computation.

So:

CeylonMigrationBot commented 12 years ago

[@quintesse] > But we have nothing equivalent to ? for working with null.

Didn't you say in your first message that this was equivalent to exists y then f(x,y) ?

actually, it might be ambiguous. So we might need to prevent that anyway.

But wouldn't that immediately make everything much less powerful? And if you need something like that you'll still need to make some kind of mix of the new syntax with the traditional syntax, right?

Do people agree we need some way to reduce the burden of explicit nulls?

Sure.

Does the above intuition make my suggestion easier to read and understand?

Doesn't your last example look very much like @FroMage 's suggested ! operator?

CeylonMigrationBot commented 12 years ago

[@RossTate] > Doesn't your last example look very much like @FroMage 's suggested ! operator?

Yes. Besides syntax, I think the difference is that for mine the null case is meant to be handled whereas for @FroMage's the null case is being asserted to not exist at all. These could be combined with expr assert message being shorthand for (essentially) expr else throw AssertionException(message).

CeylonMigrationBot commented 12 years ago

[@quintesse] Ok, besides thinking that it would still be nice to have a way to say: "I know this will never be null, trust me" what I have "against" this suggestion is that it looks nice for this simple example, but the moment that the code inside the inner-most if is more than a couple of lines and there is repeated use of the different elements (the a[] in your example) it all gets a lot less nice, you'd have to use the ? repeatedly all over the place. That's one thing that the exists at least gives us.

CeylonMigrationBot commented 12 years ago

[@chochos] There's already an assert keyword for the "trust me I now this will never be null" thing. Well it's more like a "ok compiler now you know it will never be null, too - if it is an exception will be thrown" but the thing is you don't need to nest a bunch of exists anymore (and when we merge the condition lists it will be even better). So soon you'll be able to do:

assert(exists a = maybeA && b = maybeB && c = maybeC);
a.doSomethingWith(b);
b.doSomethingWith(c);
c.doSomethingWith(a);
CeylonMigrationBot commented 12 years ago

[@gavinking] Look, @quintesse and @FroMage have been the loudest voices complaining that they don't want to have to write:

if (exists x) {
    if (exists y) {
        if (exists z = y.z()) {
            return z.f(x);
        }
    }
}
return null;

OK, I get that, and I completely agree. So, of course we could let you write this as:

return exists x && 
       exists y && 
       exists z=y.z() 
    then z.f(x);

This can be made to work. OK, so it's a whole bunch of really PITA difficult work on my side of things but hey, it can be done.

The thing is, nobody seems to be very happy with that. I have heard repeatedly from you guys that you really want a ?. operator to be able to write the above as:

return exists x then y?.z()?.f(x);

Which makes sense to me, because it's just so much less verbose, if a little more cryptic. Fine. But then if we're prepared to swallow the cryptic nature of ?. then frankly I would much prefer to generalize this into a general-purpose ? operator that would let me avoid all of the above verbosity (along with the pain and suffering of implementing flow-dependent typing within expressions). If I'm going to let you write exists x then y?.z()?.f(x), and that's not too cryptic then why in hell not let you write:

return y?.z()?.f(x?);

Sure, it's cryptic to a Java developer, but so is exists x then y?.z()?.f(x).

I want a language that has a consistent solution to problems. Not a language that has inconsistent little bits and pieces for solving parts of problems.

CeylonMigrationBot commented 12 years ago

[@gavinking] > Didn't you say in your first message that this was equivalent to exists y then f(x,y) ?

No I did not. Go back and read my message again. I said "meaning, approximately" and then gave a piece of code that does not currently pass the typechecker.

CeylonMigrationBot commented 12 years ago

[@quintesse] > but so is exists x then y?.z()?.f(x).

No it isn't, many have said it already, it reads from left to right, if something fails to match/be true then you stop reading. Everything up to there will have been executed or evaluated. But in the other example you start from the left, and then suddenly appears this x? which can somehow short-circuit everything that went before. At what point did execution / evaluation stop?

I mean, imagine:

return y?.z()?.f(y.foo, x?, y.bar);

if x is null does y.foo get evaluated? Does y.bar? If they don't (which I guess is the right answer), why not? That's normally how things get evaluated, right? That's how I imagine people will react. The same in traditional code is just obvious even if it is more verbose:

    if (exists x) {
        return y?.z()?.f(y.foo, x, y.bar);
    } else {
        return null;
    }

If the above code is the correct translation then I'd rather see that. Or make the shorter form in your original message work (see below).

No I did not. Go back and read my message again

I did read your message and I said "equivalent", which does not mean "exactly the same". Of course it means that exists would have to take immediate effect in the then part of the expression. But I find it nice and clear.

I also think that we added the else operator for a reason and totally agreed when it was decided to prefer it over ? and talked about removing ? and only keeping else because it was so much clearer.

I think the same holds true here. So even though ? is really short, I prefer seeing exists y then f(x,y) over f(x, y?). Also because we have here unrealistically easy to understand examples. We'll have to deal with method calls that take 8 parameters spanning 15 lines of code where each parameter itself consists of other method calls etc. Only to have this single, easy to miss, ? somewhere that could short-circuit the entire call.

I want a language that has a consistent solution to problems

But is it consistent to have to say "It's not completely clear to me that combining [] and ? in the same expression is OK—actually, it might be ambiguous. So we might need to prevent that anyway.". It might be from the point of the language designer, but will it be for the language user? Maybe it will, but if we have to think about it, won't they?

CeylonMigrationBot commented 12 years ago

[@RossTate] > If they don't (which I guess is the right answer), why not? That's normally how things get evaluated, right?

This is why I was saying we should use the order consistent with what people are used to. Then you just read the code in evaluation order. It's just as if some subexpression threw some exception (i.e. a NullPointerException), here we're just catching and handling it.

We'll have to deal with method calls that take 8 parameters spanning 15 lines of code where each parameter itself consists of other method calls etc.

Imagine taking a big expression that's readable in Java, but then having to assign all intermediate expressions to temporary variables just to handle the null case, making the flow of data more obscure (and the code much wordier). If anything the exists seems even less satisfying a solution when the expression is big, and using my (i.e. the normal) evaluation order should appease your other concerns with ?.

CeylonMigrationBot commented 12 years ago

[@gavinking] > No it isn't, many have said it already, it reads from left to right, if something fails to match/be true then you stop reading. Everything up to there will have been executed or evaluated.

This is just a totally natural consequence of the postfix notation for parameters. I can equally argue that its weird that in foo.bar(x.y) that x.y gets evaluated before foo.bar does. But unless you're a diehard fan of reverse polish notation, I don't think this has ever prevented you from understanding that expression.

But in the other example you start from the left, and then suddenly appears this x? which can somehow short-circuit everything that went before.

Huh?

Assuming the "modest" interpretation described above, it doesn't short-circuit anything other than the immediately containing argument list. i.e. the only thing that gets short-circuited is the method invocation itself.

At what point did execution / evaluation stop?

Well that's the whole point of the long discussion between me and Ross. The question is: is there a more "ambitious" interpretation that is still understandable. It would seem that you should be willing to take part in that discussion, since it seems you would have an opinion.

return y?.z()?.f(y.foo, x?, y.bar);

if x is null does y.foo get evaluated?

Yes.

Does y.bar?

I'm not sure. We would have to define that. There are reasonable arguments for and against.

If they don't (which I guess is the right answer), why not?

I don't know why you would assume that they don't. Nowhere in my proposal did I mention anything about ? having the side-effect of changing the order in which function arguments are evaluated, and I would not be in favor of that.

That's normally how things get evaluated, right? That's how I imagine people will react.

Right. Why do you think I would want to change it?

I think the same holds true here. So even though ? is really short, I prefer seeing exists y then f(x,y) over f(x, y?).

OK, then you're in favor of removing ?. I assume. Please correct me if that's wrong and in fact you're merely being totally inconsistent.

We'll have to deal with method calls that take 8 parameters spanning 15 lines of code where each parameter itself consists of other method calls etc. Only to have this single, easy to miss, ? somewhere that could short-circuit the entire call.

I don't know what you're talking about. Example? Or is this only under the strange interpretation where a ? on one argument short-circuits all the others?

P.S. if short-circuiting an argument evaluation changes the semantics of the code, it's because you're calling a method with side-effects from an argument list. That's simply a Bad Thing To Do. I hope I never have to see code where an argument list spanning 15 lines of code has a little side-effect hidden in one of the argument expressions. Ugh, makes me feel a little ill just thinking about it. Fortunately, I don't think I have ever met any code like that in the wild.

So even though ? is really short, I prefer seeing exists y then f(x,y) over f(x, y?) over f(x, y?).

Ok, great, assuming that y is a non-variable, non-default value reference. Do you prefer:

exists z = a && exists y=z.g(0) then f(x,y) 

over:

f(x, a?.g(0)?)

I personally don't. Indeed, I think it's quite hard to argue that the latter example isn't much easier to quickly grasp, assuming you know what ? means.

Maybe it will, but if we have to think about it, won't they?

Terrible. Asking developers to think. Can't have that. Next thing people will start treating them like they have the capacity to learn something new.

CeylonMigrationBot commented 12 years ago

[@gavinking] FTR, I wrote my last post before reading @RossTate's response.

This is why I was saying we should use the order consistent with what people are used to. Then you just read the code in evaluation order.

Right. This is what I've always just implicitly assumed would be the behavior. It seems to be what people would intuitively expect, and I don't see a reason to violate that expectation.

Imagine taking a big expression that's readable in Java, but then having to assign all intermediate expressions to temporary variables just to handle the null case, making the flow of data more obscure (and the code much wordier).

Exactly the point I was trying to make with my f(x, a?.g(0)?) example.

CeylonMigrationBot commented 12 years ago

[@quintesse] > It's just as if some subexpression threw some exception

Ok, that's an interesting way of looking at it, thanks for the mental picture.

But then I'd say this is only interesting for a certain set of cases, right? Because there will still be many cases where if I have this:

foo().bar.f(x, y?)

where I don't want foo() to be executed, nor bar evaluated if I already know I won't be able to execute f anyway. So for those cases I'm still left with the only option of if (exists), right?

OK, then you're in favor of removing ?. I assume.

You assume wrong, I was only talking about the case where we introduced else when we already had ? doing almost the same thing and then decided else almost always was the better option because it's actually a bit more useful and because it reads better. I only gave it as an example of another case where we preferred something wordier.

P.S. if short-circuiting an argument evaluation changes the semantics of the code

I'm not talking about the semantics, I'm talking about looking at a large block of code, with lots of intricacies (possibly having their own sub-calls with their own ? operators etc). And you look at it and you think it will be executed, but no, now you'll have to go over all the parameters one by one and look if one of them has a ?, because if that's the case the method might never get called at all.

It's this code (adjusted example from javac code):

        if (errorCount() == 0 && origOptions != null) {
            apt.main(roots,
                     classes,
                     origOptions, aptCL,
                     providedFactory,
                     productiveFactories);
        }

versus

        if (errorCount() == 0) {
            apt.main(roots,
                     classes,
                     origOptions?, aptCL,
                     providedFactory,
                     productiveFactories);
        }

Imagine taking a big expression that's readable in Java, but then having to assign all intermediate expressions to temporary variables just to handle the null case, making the flow of data more obscure (and the code much wordier)

Oh I'm definitely not saying I think that's better, in fact I think I talk for most of us here if I say that I hate it. And if this is really the only thing we can come up with we should probably do it.

But at the same time it seems unfortunate that we'll have nice short (although a bit cryptic) code for expressions while still having the ugly bulky (but easier to understand) version for if-statements and such. We'll still need many of those as well.

CeylonMigrationBot commented 12 years ago

[@RossTate] > Because there will still be many cases where if I have foo().bar.f(x, y?) where I don't want foo() to be executed, nor bar evaluated if I already know I won't be able to execute f anyway. So for those cases I'm still left with the only option of if (exists), right?

Good point. This just addresses what I expect will be a common case, but just as with normal effectful expressions sometimes the common case isn't sufficient.

I like your apt.main example. It's a good argument against implicitly capturing the ? case and requiring apt.main(..., origOptions?, ...) else null; to keep the control flow from being hidden.

But at the same time it seems unfortunate that we'll have nice short (although a bit cryptic) code for expressions while still having the ugly bulky (but easier to understand) version for if-statements and such. We'll still need many of those as well.

I agree. The overall concept is called path-dependent typing. @gavinking, I'm not sure how the type checker is implemented, but there are techniques for incorporating the control flow leading to a location into type checking that location that may make it easier to implement.

CeylonMigrationBot commented 12 years ago

[@tombentley] > P.S. if short-circuiting an argument evaluation changes the semantics of the code, it's because you're calling a method with side-effects from an argument list. That's simply a Bad Thing To Do. I hope I never have to see code where an argument list spanning 15 lines of code has a little side-effect hidden in one of the argument expressions. Ugh, makes me feel a little ill just thinking about it. Fortunately, I don't think I have ever met any code like that in the wild.

This made me think: "How would you know an argument expression had a side-effect?". The only way you could know was from the doc of all the methods and attributes in the argument list. I always liked the convention (is it from scheme?) where functions with side effects have a name ending in a !. Made me wonder if we could use an annotation for a similar effect, so a method annotated impure was displayed differently in the IDE (in italics say). Sorry this is completely tangential to the discussion, but I though I'd at least mention the idea...