jashkenas/coffeescript

reconstruction feature

mark-hahn opened this issue · 23 comments

I have asked for this before but I have now run into about a dozen places in my app where I have code like this (production code) ...

view = 
    id: doc._id
    thread: doc.thread
    lastEdited: doc.lastEdited
    name: doc.name
    posted: doc.posted
    html: doc.html
    ptrNum: ptrNum
    okToEdit: (doc.author is userId)

... and I would really like to shorten it to ...

view = {id, thread, lastEdited, name, posted, html, 
        ptrNum:ptrNum, okToEdit: (doc.author is userId)} = doc

This allows one to select a subset of an object to create a new object. I do this all the time. In this particular example it is doing deconstruction and then reconstruction in one operation.

I don't really care what the syntax is, I just don't want to repeat the source object over and over. Another syntax option would be something like this (just replace the right = with "in") ...

view = {name, id, age}  in  doc

The token "in" could be any token/operator that doesn't break the language. The token means "use the following as the default object for this constructor instead of the local scope".

Each time I write code like this I pine for the feature more and more. How hard would it be to add this feature? I would be willing to spend whatever time it takes to code this for a pull request.

To fix this in the near-term, you could do something like this:

@mix = (obj, keys) ->
  if keys
    @[key] = obj[key] for key in keys
  else
    @[key] = value for own key, value in obj
  this

view = 
  some: "stuff"

# copy certain properties
mix.call view, doc, ["name", "id", "age"]

# copy all direct properties
someObj = mix.call {}, doc

That's funny. I was thinking about this exact same thing about an hour ago. I was thinking of extending the underscore extend function. If a param on the right was an array it would use the first item as the object and the rest as allowed property names. This way you could have multiple docs on the right as _.extend normally does.

_.extend view, [doc, "name", "id", "age"]

That's good, too. I'm glad I came across this. I hadn't thought much of "selective mixins" until I read over your problem. This would be a great CS feature.

I just put this in my production code ...

oldExtend = _.extend

_.extend = (dest, sources...) ->
    for source in sources
        if _.isArray(source)
            srcObj = source[0]
            for key in source[1..]
                dest[key] = srcObj[key]
        else oldExtend dest, source
    dest
$ coco -bpe 'view = doc{name, id, age}'
var view;
view = {
  name: doc.name,
  id: doc.id,
  age: doc.age
};

I second this. I often do things like:

{id, title, content} = req.body
post = {id, title, content}

I would love to be able to shorten it to:

post = {id, title, content} = req.body

Concise and clear, IMO.

The coco syntax is almost better, though. No extraneous variables.

I'm fond of that coco syntax as well.

edit: I mean, with the .: doc.{name, id, age}

confusion with fn {...}

Inevitable as long as we have implicit call:

$ coffee -bpe 'a[b]; a [b]'
a[b];
a([b]);

Avoiding it by using longhand is possible though:

$ coco -bpe 'doc . {id}; a . [b]'
({
  id: doc.id
});
a[b];

Yeah, the Coco syntax

doc.{name, id, age}

is really nice. Provides DRY for a very common use case. I'd expect rave reviews if it were added in 1.2.

That would just expand into:

[doc.name, doc.id, doc.age]

?

@cpryland No, try it out in the Coco console: http://satyr.github.com/cup/

You can write

doc1 = {name: 'blah', id: 'asfg', age: 33}
doc2 = {}
doc2.{name, id, age} = otherDoc

and that last line is equivalent to doc2.name = doc1.name; doc2.id = doc1.id; doc2.age = doc1.age. You can also write

doc3 = doc2.{name, id, age}

and doc3 is a new object with those properties.

So, it's a nicely symmetric form of the extends syntax that's been requested so many times.

#1632 is slightly related

#1708 is much more related

@accelware but it's using the curlies! If we had a similar syntax for returning an array of multiple properties, I'd expect it to use square brackets.

array = doc2.[name, id, age]
doc3.[name, id, age] = array

As it turns out, coco has this feature as well and I really like it.

I recently tried:

subset = { a, b } = set

And was disappointed when it didn't do what I expected, which was to create subset as a hash slice of set. I'd like to see this feature in CoffeeScript, so +1.

@bigeasy: Wouldn't you prefer the syntax from #1708? subset = set.{a, b}

@michaelficarra I sure would. In fact, I thought I had commented on that issue saying as much, but it seems I must have 👍ed that syntax in another, related issue. 8|

@michaelficarra I didn't mean to state a syntax preference. Only noting that I'd been hoping for a way to do hash slicing.

+1 fo' sho'!

i suggested this a while ago, for use of something like

run_function( { key1, key2 } = object )

for now, I've been using

run_function( _.pick object, 'key1', 'key2' )

I like the sub = object.{key1, key2} expression syntax :)

It is very confusing how sub = {key1, key2} = object works in my opinion. But i think both possible behaviours of that statement are confusing (i.e. assigning sub to object or to {key1, key2}); i'd prefer that destructuing assignment would not be an expression, so that statement is invalid (just like foo = return is invalid).

Closing this request as it’s not a priority at the moment. That said, if someone feels like implementing this as a PR that doesn’t introduce any breaking changes, it would be considered.