3 ms·
CoffeeScript was great except for a few fatal mistakes. The biggest was implicit variable declarations, which meant that to write maintainable code, you had to
by mistercow 2y ago
CoffeeScript was great except for a few fatal mistakes. The biggest was implicit variable declarations, which meant that to write maintainable code, you had to reintroduce explicit declarations via iifes. Otherwise, you’d risk a future code change introducing shadowing which would have consequences that were super painful to debug.
The other big one, which unfortunately Civet seems to be doing too, is implicit return combined with “everything is an expression”. You can have one of those, but not both. With both, it’s far too easy to write loops intended to be statements, and which accidentally turn into gigantic multi-dimensional array returns which can’t easily be optimized out by the compiler. Fortunately, this would be only mildly inconvenient to work around with a lint rule that forces explicit return in all functions.
- edemaine 2y agoYou can also turn off implicit returns in Civet if you don't like them. They also work well with TypeScript annotations: if you annotate a return value of `void`, then there's no implicit return. I agree it can be easy to make and throw away big arrays if you're not aware of what's going on. But it can also save a lot of time. For loops as arrays are super useful, integrating the equivalent of "map" into the language itself. We also recently added generator versions (for*). JSX is a nice example where for loops as expressions and implicit return are powerful; see e.g. https://civet.dev/reference#code-children https://civet.dev/reference#code-children
- mistercow 2y agoYeah those examples are compelling. What might work well is a lint rule to error if a loop expression ends an actual function declaration (i.e. not an inline callback), and the function doesn’t explicitly define a return type. I think that catches almost every bad case, aside from the odd memory leak in really unusual edge cases.
- scotty79 2y agoYou can have shadowing with explicitly declared variables. And there are languages that allow that, Rust to name one and I love that I can do that every time I use it.
- mistercow 2y agoI slightly misspoke. The issue was that you wanted shadowing, but because of the lack of declarations, some random inner variable could suddenly reference a variable with broader scope. For example, you might have an “x” in some inner loop within a function. Then later on, you’d add an “x” earlier in the function, not realizing that that name was used down below. Suddenly the inner loop is referencing that new variable, and there was no way to tell other than the surprising ways your code would suddenly malfunction.
- scotty79 2y agoAh, I see, you are right. It's terrible behavior.
- chrismorgan 2y ago> implicit return combined with “everything is an expression”. You can have one of those, but not both. Hang on, how can you have “everything is an expression” without implicit return? If I’m correctly understanding what you’re talking about, implicit return isn’t actually a thing—it’s just that the function body is an expression too. (Never dealt with CoffeeScript, but I’ve been writing Rust for over a decade now, and learned to love expression-orientation there.)
- int_19h 2y agoYou do it by making sequential evaluation (i.e. the semicolon, which becomes an operator in "everything is an expression" world) always produce a void result. Then you introduce the notion of terminating evaluation early and yielding a value from the compound expression - this becomes `return` (and other similar constructs like `break` and `continue`).
- chrismorgan 2y agoAh yes, true, true, I had forgotten about the semicolon. Somehow. Yeah, I don’t think I’d like expression-orientation so much without it.