eclipse-archived/ceylon

use 'let' instead of 'value' for destructuring statements

Closed this issue · 9 comments

For Ceylon 1.4/2.0/whatever, I've decided to change the keyword used to write a destructuring statement, since:

  • the fact that things like value value x are in principle (and practice) accepted by the grammar is a source of confusion and distaste and much polemic, and
  • destructuring statements are considered to be like assertions (and control structures), but their grammar is irregular, since (a) you can't define multiple patterns separated by commas, and (b) they don't feature the parens that assert requires.

Therefore, I've changed the syntax to something a bit more regular. For example:

void fun([String, Integer->Float] pair, Float[3] point) {
    let ([key, pos->intensity] = pair, 
         [x, y, z] = point,
         d = sqrt(x^2+y^2+z^2));
    ...
}

I think this removes a lot of potential for confusion.

Note that, as with every variable in a control structure in Ceylon, each pattern variable is a whole little value declaration. The above could be written, more verbosely, like this:

void fun([String, Integer->Float] pair, Float[3] point) {
    let ([String key, Integer pos->Float intensity] = pair, 
         [Float x, Float y, Float z] = point,
         Float d = (x^2+y^2+z^2)^0.5,
         value sum = x + y + z);
    ...
}

Here we can see the difference between let and value.

The previous syntax is deprecated and results in a warning.

I have pushed this on the 7220 branch. It's working except for one little bug. The following doesn't compile correctly on either backend:

let (x=1);

(i.e. this is the case where we have a plain variable instead of a pattern.)

I'll need the help of @FroMage with fixing this.

Sorry, the issue with the JS backend is at runtime. And actually I can fix it myself with a little hack to the AST I generate (I can unpack the Variable into a Destructure with a VariablePattern).

But the Java backend problem is a little trickier.

Ah, cool, I found a transformation I could do to the AST to make this compile on both backends.

Since this is now working correctly, I have merged the branch.

Still to do:

  • spec
  • backend tests
  • fix the backends and remove the hack (?)

I have already updated the SDK to use this new syntax.

@gavinking

I really think you are taking the destructuring syntax the wrong way here. I think the syntax for destructuring should be brought closer to the syntax of regular declarations, and not further from it. As I've said on Gitter, I feel like destructuring is simply a declaration of multiple values, and a declaration is simply the destructuring of a single value.

Consider Javascript's syntax, for example. Notice how regular it is:

let  foo  =  "foo";
let [bar] = ["bar"];

Now, take a look at Ceylon's syntax today, notice how it resembles Javascript's syntax in being regular:

value  foo  =  "foo";
value [bar] = ["bar"];

Now take a look at your proposed syntax, notice how different a regular declaration looks from a destructuring declaration:

value foo = "foo";
let([bar] = ["bar"]);

Here, none of them is derived from the other; they are clearly completely different syntactical constructs. It simply doesn't make sense to syntactically separate two semantically similar concepts like this.

If you took a look at my ideas for pattern matching and destructuring on our private conversation on Gitter, I believe you'd be agreeing with me.

Either way, @gavinking, it really bothers me that no one seems to be tackling neither your ideas nor mine in relation to destructuring -- it's almost as if we were the only ones that actually cared about Ceylon's syntax's regularity.

@Zambonifofex

let  foo  =  "foo";
let [bar] = ["bar"];

Our previous syntax looked exactly like that and you hated it and spent many months obsessing over it here, on gitter, and in private messages.

You made enough noise about the issue, that I wound up changing something which simply wasn't bothering me or anybody else, since, as you rightly pointed out, it wasn't quite perfectly regular.

The new syntax is perfectly regular in Ceylon. Ceylon is not JavaScript, and so what's regular in Ceylon is different to what is regular in JavaScript. In particular, Ceylon has optional type declarations on variables. Ceylon lets me write this:

let ([Float x, Float y] = [1.0, 2.0]);

or this:

let ([Float x, Float y] = [1.0, 2.0]);

Which is simply not the case in JavaScript.

So the languages are different.

Look, I have heard you loud and clear, I've taken your opinion into consideration, I've explored a whole range of options over the space of many months, and ultimately we disagree. I'm not going to make a change to the syntax of that language that I think is wrong just because it's the only way to make you shut up about the whole thing.

I simply don't wish to discuss this issue any further. Please get used to the new syntax, because that's what the syntax is going to be in 1.4.

I wrote above:

Ceylon lets me write this:
let ([Float x, Float y] = [1.0, 2.0]);

or this:

let ([Float x, Float y] = [1.0, 2.0]);

Which is simply not the case in JavaScript.

Actually that misstates the important difference. The important difference is that, in Ceylon, value is not a required part of a "value statement". We can leave it off when we write in types instead. This is legal:

Float x = 1.0;

That's not allowed in JavaScript, of course.

But types logically belong to the variable declaration occurring within a pattern, like this:

[Float x, Float y]

Not to the whole pattern—nor to the statement to which the pattern belongs—like this:

Float[2] [x, y]  //ugly

But, in addition, types are always optional in patterns. Combining these two rules, leads to the expectation that:

[x, y] = [1.0, 2.0];

is a legal destructuring statement. Oops! That's not OK.

Let me clearly restate these two rules, since they are the fundamental constraints we have here, that are really basic to the syntax of Ceylon:

  1. value (or function) can be replaced by an explicit type like Integer, and one of the two is required for a toplevel value declaration statement, but
  2. neither keyword nor type is required of a pattern (or control structure) variable.

The only satisfying solution I've seen to this problem is to say that:

  • a value declaration can occur either as a statement, or within a pattern, but
  • a pattern must occur within a control structure, not as a toplevel statement.

So this is legal:

let ([value x, value y -> value x] = ... ); //value decs in pattern in control structure

As is this:

value x = .... ; //toplevel value dec

But not this:

[value x, value y -> value x] = ... ; //value decs in toplevel pattern (disallowed)

And that's all completely satisfying to me. A value declaration declares a single label, not a patternfull of multiple labels.

Obviously none of the above discussion applies to languages where you can't replace value with a type:

  • it doesn't apply to dynamic langs like JavaScript
  • I guess it probably doesn't apply to languages with postfix type decs like Scala. (In Scala the value at the start of a value statement is always required. You can't leave it off when you declare a type.)

This is all quite a subtle and complex issue, of course.

types logically belong to the variable declaration occurring within a pattern

Note that one of the alternative solutions I originally considered, when we first designed destructuring, and which is completely internally consistent, is to reverse this assumption, and say that types belong to whole patterns, not to pattern variables. Then one could write:

Float[2] [x, y] = [1.0, 2.0];

And:

value [x, y] = [1.0, 2.0];

And:

for ([x, y] in pairs) {}

And:

for (Float[2] [x, y] in pairs) {}

Etc.

But not:

[Float x, Float y] = [1.0, 2.0]; //would not be allowed

Nor:

for ([Float x, Float y] in pairs) {} //would not be allowed

In retrospect I do wonder if this might not have been a batter path to go down, however, there are two real major problems with this solution:

  • it breaks syntax that has been accepted since pre-1.0 versions of Ceylon, along with
  • the nice syntax for pattern matching in case, i.e. case ([Integer i, Integer j]).

I don't think anyone really wants to write:

case ([String,Float] [name, x])

Instead of:

case ([String name, Float x])

So, anyway, we didn't go down that path, and I don't see a good reason to break lots of code to reverse that decision now.

No opinion on this.

I have added backend tests. Closing.