satyr / coco

Unfancy CoffeeScript
http://satyr.github.com/coco/
MIT License
497 stars 48 forks source link

New syntax proposal: Constructor Cascade (returns the cascadee) #172

Closed qqueue closed 12 years ago

qqueue commented 12 years ago

I've got a fair amount of code that looks like this:

head = document.createElement \head
style = document.createElement \style
  &id = \html5chan-style
  &textContent =
    '''
    %hakase.css%
    '''
head.appendChild style

title = document.createElement \title
  &textContent = board.title
head.appendChild title
html.appendChild head

or more purely, like this:

thing = new Thing
  &foo = 'bar'
  &baz = quux 'stuff'
something-with thing

where some of the code initializes and mutates an object just to pass it into another function (or return it). When available, I usually use a full constructor:

something-with new Thing foo: \bar baz: quux \stuff

But some constructors, e.g. DOM Image, have stupid arguments (no src), so an extra line of initialization is necessary.

I know I can use new with a block to create the new function() {...} form, but that doesn't work with Image, and it introduces overhead. Thus, my straw man is:

Constructor Cascade

Similar to cascade, but expression returns the original reference (after execution of the block) instead of the result of the last expression in the block.

fn = -> construct new Image
  &src = 'image.jpg'

document.body.appendChild do
  construct document.createElement \div
    &id = \stuff
    &classList.add \things
    &style
      &display = \block
    &textContent = 'are you a wizard'

=>

var img, $x,$y;
img = function() {
  var $y = new Image
  $y.src = 'image.jpg'
  return $y
}

$x = document.createElement('div')
$x.id = 'stuff'
$x.classList.add('things')
$y = $x.style
$y.display = 'block'
$x.textContent = 'are you a wizard'
document.body.appendChild($x)

Advantages

satyr commented 12 years ago

I have to agree that a cascade expression should evaluates to the cascadee value. While maximally minimal, the current syntax seems to miss the major use cases by limiting itself to the top-level.

We add no new keywords as a policy, so we may have to introduce some new symbol operator. Say, +>:

something-with new Thing +>
  &foo = 'bar'
  &baz = quux 'stuff'

And/or we can make with return the target by default:

something-with with new Thing
  @foo = 'bar'
  @baz = quux 'stuff'
qqueue commented 12 years ago

Hmm, is it even worth keeping with's current behavior as sugar for .call(thing)? I can't imagine many use cases where having the actual IEFE would be preferrable over the more performant cascade. I mean, with's example in the docs could be completely replaced with cascade. If one actually needs a new scope, let makes more sense than with anyway.

Using with as the cascade-that-evaluates-to-cascadee operator avoids using creating any more reserved words, and it "reads" better than +>, IMO.

For the times that you do need to set the context for an IEFE, you could just special-case let to detect constructs like let @ = something or let this = something to set the context appropriately.

satyr commented 12 years ago

Great point.

The purpose of with was exactly the cascade-ish construct. Now that we added an actual cascade, we should reconsider its raison d'etre.

So the proposal becomes:

qqueue commented 12 years ago

That looks fine to me, though the readability of the compiled js suffers a bit with the comma expression. Similar to #115, it'd be nicer if it compiled to

head.appendChild(
  (x$ = document.createElement('title')
  , x$.textContent = btitle
  , x$))

Just a minor issue.