Closed showell closed 7 years ago
Here is a more symmetrical terminology for classifying the four forms:
fn = -> a(); b() # "function literal"
fn = -> # any name other than "function literal"
a()
b()
Pretty crazy to call those two semantically equivalent constructs different things, right? That's how I feel about differentiating "comprehension" and "array expression" in the first post. Same thing with then
/indent swaps. It's just an alternative syntax that generates the exact same AST. I defer to my descriptions in #2030 for my opinion on the proper categorisation and terms.
precedence
You've been using this word a lot, but I think you mean to say "precedents".
I don't know why it's crazy to come up with different names for two syntactical forms. Sure, both of your examples are functions literals, but the first one uses the single-line function literal form, while the second one uses the block function literal form.
What possible benefit do we get from vague terminology?
There are precedents all over the place for using names to distinguish alternative syntaxes of the same semantic constructs. You have prefix if vs. postfix if. You have infix arithmetic expressions vs. RPN. You have whitespace-based blocking vs. brace-based blocking.
@showell:
I don't know why it's crazy [...]
Because they're indistinguishable to the compiler. They just look different to us humans. So they're not actually different.
[...] vague terminology?
It's not vague when it describes a set of things that are indistinguishable from one another. Go ahead and call the syntaxes whatever you like -- single-line, multi-line, whatever -- it doesn't change the fact that they all cause indistinguishable AST nodes of type Code
to be inserted into the AST.
There are precedents all over the place for using names to distinguish alternative syntaxes [...]
Correct, and those names are all defining syntactic properties. Here, we are trying to categorise/distinguish the set of all AST nodes generated with any syntax containing for
/while
/loop
/unless
.
They just look different to us humans. So they're not actually different.
But shouldn't documentation be written for us humans?
@michaelficarra Who is "we"? You may be trying to distinguish sets of AST nodes, but I'm trying to distinguish syntactical forms, which is more important to most CoffeeScript users. The context of this discussion is the documentation here:
It's a syntax document:
@geraldalewis Documentation written for humans? Now that's just crazy talk. :)
@michaelficarra I am reminded that there are only three truly difficult things in computer science: coming up with good names for things and avoiding off-by-one errors. Gotta run. Look forward to more discussion later in the day.
Here is an example cut at the docs that starts with the imperative forms first.
CoffeeScript supports several looping constructs, with two interesting variations. First, you can use natural-language variations of constructs that emphasize the "body" of the loop. Second, loops can be turned into array expressions (aka "comprehensions").
Let's begin with traditional imperative loops.
The for-in loop operates on arrays.
for food in ['toast', 'cheese', 'wine']
prepare food
eat food
clean_dishes()
courses = ['greens', 'caviar', 'truffles', 'roast', 'cake']
for dish, i in course
menu i + 1, dish
return
The for-of loop allows you to iterate over the keys and values of an object:
children = joe: 10, ida: 9, tim: 11
for child, age of children
console.log "#{child} is #{age}"
return
The "while", "loop", and "until" statements allow low-level looping:
num = 6
while num -= 1
sing "#{num} little monkeys, jumping on the bed.
One fell out and bumped his head."
loop
repeat_same_task() # infinite loop
until x > 1000
x = x * 2
console.log x
Imperative loops can be written in postfix form to emphasize the actions:
eat food for food in ['apple', 'banana']
console.log "#{child} is #{age}" for child, age of children
shout "Listen to me!" until heard
You can use the "when" construct to avoid actions on certain loop values:
eat food for food in foods when food isnt 'chocolate'
Now we get to a powerful and fundamental feature of CoffeeScript--loops can be turned into array expressions.
Let's start with a simple example:
cubes = (
for x in values
x * x * x
)
The above code maps values to a new array called cubes, where each element in cubes is the cube of the corresponding element from values.
All loops can be turned into expressions using the model above. Also, if a loop is the final statement executed in a function, then the loop is also implicitly turned into an array expression.
child_statements = (ages) ->
# this function returns an array of strings
for child, age of ages
"#{child} is #{age} years old"
# Nursery Rhyme
num = 6
lyrics = while num -= 1
"#{num} little monkeys, jumping on the bed.
One fell out and bumped his head."
When you create an array from another array, you sometimes want to emphasize the mapping function, placing it first in the expression. This syntactical construct is called a list comprehension.
# return an array of cubes, using only even numbers
# from the source array
cubes = (x*x*x for x in nums when x % 2 == 0)
All looping constructs can be turned into comprehensions.
whitespace_rows = (row while is_whitespace row = getrow())
Folks coming from Python and other languages should understand the following behaviors of CoffeeScript for nested comprehensions:
When using a JavaScript loop to generate functions, it's common to insert a closure wrapper in order to ensure that loop variables are closed over, and all the generated functions don't just share the final values. CoffeeScript provides the do keyword, which immediately invokes a passed function, forwarding any arguments.
for filename in list
do (filename) ->
fs.readFile filename, (err, contents) ->
compile filename, contents.toString()
@geraldalewis @jashkenas @TrevorBurnham @michaelficarra
See my prior comment on this thread for a proposed rewrite of the "Loops and Comprehensions" section of the docs.
First, I introduce headings to call out the different forms that can be used:
Also, I try to lead with the most common form of loops, which are the imperative loops. I'm pretty sure this will put newbies on familiar ground right away, and I think that even advanced programmers will still want to learn the imperative form first. By starting with simple imperative loops, I am able to introduce for/of much earlier than the current docs do.
The current “Loops and Comprehensions” section of the docs is almost exactly @showell’s comment above, minus the sub-headings which I feel make things more intimidating than they need to be. Closing as this has already been done.
There are a couple open issues on nested comprehensions, where folks ask whether CoffeeScript should be more like Python and Haskell. However those issues get resolved, we can probably start by documenting the current behavior more precisely.
First, without getting into abstract definitions, it's pretty easy to call out the two main differences between CoffeeScript and Python:
Second, it's important to call out the distinction between these lines in coffeescript:
Third, it's important to distinguish these two syntactical constructs (which produce the same code):
The two pieces of code are identical semantically, but they are different syntactically. IMHO we should use a precise definition of "comprehension" that refers specifically to the first syntax. Wikipedia and Python's PEP both provide precedence for calling out comprehensions as a specific syntactical notation for producing lists. Wikipedia emphasizes the analogy to set builder notation, which specifically puts the output function first in the expression.
I propose these definitions:
I predict these definitions will be up for extensive debate. See the discussion between me and @michaelficarra at issue 2030.
Once the definitions get nailed down, I propose that we reorganize the docs to show all four forms separately, even if we can't agree to come up with four different terms for the forms.
Other related issues: