gkz / LiveScript

LiveScript is a language which compiles to JavaScript. It has a straightforward mapping to JavaScript and allows you to write expressive code devoid of repetitive boilerplate. While LiveScript adds many features to assist in functional style programming, it also has many improvements for object oriented and imperative programming.
http://livescript.net
MIT License
2.31k stars 156 forks source link

question about import operator <<< #1080

Open determin1st opened 5 years ago

determin1st commented 5 years ago

should this code work?

a =
 p1: 1
 p2: 2
<<<
 p3: a.p1 + a.p2
 p4: 4

a

because, this one works:

a =
 p1: 1
 p2: 2
<<<
 p3: a.p1 + a.p2

a

also, when used import all, it works:

a =
 p1: 1
 p2: 2
<<<<
 p3: a.p1 + a.p2
 p4: 4

a
rhendric commented 5 years ago

I can imagine two possible reasonable compilations for

a =
 p1: 1
 p2: 2
<<<
 p3: a.p1 + a.p2
 p4: 4

The first is

a = { p1: 1, p2: 2, p3: a.p1 + a.p2, p4: 4 };

The second is

(a = { p1: 1, p2: 2 }).p3 = a.p1 + a.p2;
a.p4 = 4;

(This doesn't even address cases like (a = p1: 2) <<< p1: 1, p2: a.p1; I could argue that after executing this statement, a.p2 ought to equal 2, not 1, despite what LiveScript currently does.)

As the current result of this compilation isn't equivalent to either of these, I'm calling this a bug, but I'm not sure which of my two possibilities should be the result instead. The precedence relation between = and <<< seems sensitive to braces and line breaks, and right now I'm not sure how much of that is intentional.

vendethiel commented 5 years ago

Clearly it should be parsed this way..:)

a =
 p1: 1
 p2: (2
<<<
 p3: a.p1 + a.p2
 p4: 4)
determin1st commented 5 years ago

@rhendric

Of course the second variant! Isn't it intuitive? The point is to reuse the name of newly created object and add properties that depend on the previous assignment. Your first example equals to this:

a =
 p1: 1
 p2: 2
 p3: a.p1 + a.p2
 p4: 4

I used <<< only for fast code typing, not for fast objects which should be created with constructor/prototype syntax

rhendric commented 5 years ago

What do you think about this case:

a = { p1: 1, p2: 2 } <<< p3: a.p1 + a.p2, p4: 4

Is that equivalent to the second variant or the first? How about:

f = -> p1: 1, p2: 2
a = f! <<< p3: a.p1, p4: 4

Or:

f = -> p1: 1, p2: 2
g = -> p3: a.p1, p4: 4
a = f! <<< g!

LiveScript currently interprets all of these as equivalent to the first variant, and I think that's probably correct. Do you agree?

rhendric commented 5 years ago

I think the question boils down to explaining why the following two snippets should be different (they currently are):

a = x: 0
b = x: 1

a = b
<<< c: a.x
a.c #=> 0
a = x: 0
b = x: 1

a =
  b
<<< c: a.x
a.c #=> 1

This duality exists with many other binary operators (at least, the ones that don't have a different interpretation when they start a line), such as ^, comparison operators (> etc.), boolean operators (&& etc.), composition operators (>> and <<), pipes (|> and <|), and <<<<, so at least LiveScript is consistent. The only place I can find the binary-operator-beginning-a-line pattern in LiveScript's tests is https://github.com/gkz/LiveScript/blob/bc1c188f01298567bc689c979147829c6ac57213/test/operator.ls#L317-L319 but that's a pretty clear indicator that this difference is intentional. This all favors @determin1st's preferred interpretation of his original submission; my only remaining unease is not being able to succinctly explain what the governing principle is here. It's not quite that a binary operator immediately following a dedent will take as its left operand the entire expression leading up to and including the indented block—counterexample:

a <| b do
  c
<<< d #=> equivalent to a(b(c) <<< d), not a(b(c)) <<< d

Maybe a binary operator just gets a lower but non-zero precedence when it immediately follows a dedent—less binding than =, but more binding than <|? It's not a fixed lower precedence, though—the relative precedence between ^ and > is preserved in the below:

a do
  b
^ c do
  d
> e #=> equivalent to (a(b) ^ c(d)) > e

a do
  b
> c do
  d
^ e #=> equivalent to a(b) > (c(d) ^ e)

Anyone have any good theories for me?

determin1st commented 5 years ago

your second example, be it (a = f!) <<< g! or a = (f! <<< g!) - doesn't change anything, right? So it's correct. The first:

a = f! <<< p3: a, p4: 4

currently means, that the value of variable a, is set before the expression starts to run, like:

a = 3
a = f! <<< p3: a, p4: 4

will do a = (f! <<< {p3: a, p4: 4}), not (a = f!) <<< {p3: a, p4: 4}. but i'm also okay/agree to the second variant, because i didn't use this syntax ever. it works with objects, not primitive values, so, maybe it's a special case.

rhendric commented 5 years ago

your second example, be it (a = f!) <<< g! or a = (f! <<< g!) - doesn't change anything, right?

I'm afraid it does—it's very similar to the other example:

f = -> p1: 1, p2: 2
g = -> p3: a.p1, p4: 4

a = p1: 0
a = (f! <<< g!)
a.p3 #=> 0

a = p1: 0
(a = f!) <<< g!
a.p3 #=> 1
determin1st commented 5 years ago

well, your examples are tricky, but i feel, ill be okay with

f = -> p1: 1, p2: 2
g = -> p3: a.p1, p4: 4

a = p1: 0
a = f! <<< g!
a.p3 #=> 1