Destructuring Assignment in CoffeeScript

CoffeeScript supports a subset of pattern matching called destructuring assignment. Destructuring assignment uses patterns to extract out the parts of an object during assignment. It’s a simple and elegant technique but when overused can have unintended consequences on a codebase.

CoffeeScript’s two data structures, Arrays and Objects, can both be destructured. Let’s first take a look at destructuring Arrays.

Destructuring Arrays

The “Hello World” of destructuring assigment is using Arrays to swap two variables.

coffee> x = 1
1
coffee> y = 2
2
coffee> [x, y] = [y, x]
[ 2, 1 ]
coffee> x
2
coffee> y
1

In our assignment statement, we matched our lvalue pattern to the structure of our rvalue, swapping x and y.

Array destructuring can unpack the result of functions that return multiple values.

coffee> [firstName, lastName] = 'Foo Bar'.split ' '
[ 'Foo', 'Bar' ]

coffee> firstName
'Foo'

coffee> lastName
'Bar'

coffee> [phoneNumber, prefix, lineNumber] = '555-1212'.match /(\d{3})-(\d{4})/
[ '555-1212', '555', '1212', index: 0, input: '555-1212' ]

coffee> phoneNumber
'555-1212'

coffee> prefix
'555'

coffee> lineNumber
'1212'

CoffeeScript’s splat (...) can destructure a subset of an Array.

coffee> [head, tail...] = [1..10]
[ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]

coffee> head
1

coffee> tail
[ 2, 3, 4, 5, 6, 7, 8, 9, 10 ]

Destructuring Objects

Object destructuring is used to extract an Object’s state. Think of it as the opposite of construction.

coffee> user = name: 'Foo', age: 42
{ name: 'Foo', age: 42 }

coffee> { name: name, age: age } = user
{ name: 'Foo', age: 42 }

coffee> name
'Foo'

coffee> age
42

Again, our lvalue pattern matched the structure of our rvalue, in this case, an Object. However, the destructuring syntax to define a variable with the same name as an Object’s property is repetitive. A shorter syntax is available.

coffee> { name, age } = user
{ name: 'Foo', age: 42 }

coffee> name
'Foo'

coffee> age
42

Destructuring patterns can match an Object’s structure to any depth.

coffee> user = name: 'Foo', age: 42, address: { city: 'Anytown', state:
'AL' }
{ name: 'Foo', age: 42, address: { city: 'Anytown', state: 'AL' } }

coffee> { address: { city, state } } = user
{ name: 'Foo', age: 42, address: { city: 'Anytown', state: 'AL' } }

coffee> city
'Anytown'

coffee> state
'AL'

Here we extracted parts of the user’s nested address Object using the shorter object pattern syntax.

Destructuring Object Function Parameters

Function parameter lists can destructure Object parameters to eliminate local variables.

displayName = ({ name, age }) ->
  console.log "#{name}, #{age} year(s) old"

coffee> displayName name: 'Foo', age: 42
Foo, 42 year(s) old

Default function parameter values can be combined with destructuring.

displayName = ({ name, age } = { name: 'Unknown', age: 0 }) ->
  console.log "#{name}, #{age} year(s) old"

coffee> displayName name: 'Foo', age: 42
Foo, 42 year(s) old

coffee> displayName()
Unknown, 0 year(s) old

Destructuring Objects During Iteration

Destructuring the items in a collection while iterating over it is another common use case for destructuring.

coffee> users = []
[]
coffee> users.push name: 'Foo', age: 42
1
coffee> users.push name: 'Bar', age: 15
2
coffee> users.push name: 'Baz', age: 25
3
coffee> age for { age } in users when age > 21
[ 42, 25 ]

Does Destructuring Break Encapsulation?

Object destructuring allows you to easily extract an Object’s state. However, the code is now dependent on implementation details, increasing coupling, and making refactoring difficult. OO-purists see this as a violation of the Law of Demeter.

Anytime you find yourself destructuring an Object, ask why you’re ripping it apart. Then, look for ways to make the Object responsible for the behavior. Destructuring is very convenient, but make sure you’re aware of its effects on the cohesion of a codebase.

About Jared Carroll

After a short stint in the fashion industry Jared found his true calling at Carbon Five. Yes... he looks like a serial killer in this photo. But really he is as gentle as a flower.
This entry was posted in Web. Bookmark the permalink.
  • http://twitter.com/rudy Rudy Jahchan

    Functionality that allows you to easily slice and dice data is what draws me to Scala, Haskell, and other functional languages, so this gives me another reason to dig into CoffeeScript.

    But, while I do understand the point you are making about destructuring makes one aware of implementation details which can lead to code dependency on those details and thus introducing “brittleness”, I don’t think it’s that strong. Couldn’t this charge be made against all languages with the same functionality? Would love to hear you elaborate on those ideas.

    • Ben

      I agree that languages with list comprehension and destructuring are brittle. I think this is part of why there is such a steep learning curve for Scala and Haskell and functional languages in general.

    • Jared Carroll

      The most invasive pattern matching can get in functional languages that support it is matching against the arguments passed to the constructor of a type. I don’t feel this introduces as strong an implementation dependency that destructuring does.

      Repeating either technique multiple times in a codebase will cause interface and implementation refactorings to ripple across the codebase. I’m more pro-pattern matching in general that destructuring and would question any usage of it.

  • Anonymous

    I see what you are saying about brittleness, but like Rudy, I don’t think it’s that strong either. I envision destructuring being used more in the innards of code to provide nice, terse ways of pulling data out of an object at times when you are tightly linked to the implementation anyway. The are akin to regular expressions for strings – very dependent on the string itself for achieving the desired result. And then any time your destructuring breaks because your model has changed, your unit tests will catch it.

  • Ben

    Destructuring and list comprehension make my brain hurt. Anytime it takes me more than 2 seconds to grok a line of code it triggers a code smell response.

    age for { age } in users when age > 21

    Huh? Why not just write two or three lines of code that conveys exactly what you are doing? That way you won’t confuse the hell out of new or junior developers on your team. Writing less code is not always the answer — see Perl.

    • Anonymous

      It can be a fine line. Think about regular expressions. They are totally unintelligible if you don’t know them, but no one would say we shouldn’t use them.

      I’m not sure if destructuring is quite as useful as regex, but it seems like the same concept to me.

      • Ben

        The difference for me is that regular expressions give you features and functionality that you can only get by jumping through hoops that would be harder to understand anyway.

        • Anonymous

          Yeah, I see your point. I guess I still think that destructuring might be worth the learning curve in order to achieve terse code, but it doesn’t seem like a slam dunk to me.

      • George Gruschow

        > No one would say we shouldn’t use [regular expressions]

        ‘Some people, when confronted with a problem, think “I know, I’ll use regular expressions.” Now they have two problems.’ – jwz (http://regex.info/blog/2006-09-15/247)

    • Null

      You have a point that this example isn’t great demonstration of destructuring because one could just write:

      foo = (user.age for user in users)

      So the advantage isn’t clear.

      However, remember that the point of this article is to demonstrate destructuring in CoffeeScript and the pattern in question is useful in some instances:

      In general, you want to use nice generic functions wherever possible for maximum composability, dry-ness, clarity etc.

      Let’s say we have a function called “sendToAnalyticsDB”. This function connects to a MongoDB backend, connects to the database provided by the first argument…and then just takes the object provided and converts it to a document.

      This is a nice very generic function.

      Now, let’s say I have a list of user objects, and I want to send their name, age, and location to the DB. Using destructuring it’s as simple as:

      (sendToAnalyticsDB “users”, { age, name, location } for { age, name, location } in users)

      There are other ways you can do this….you could alter sendToAnalytics to filter out unwanted data….you could wrap it in a filter function….you could handle the filtering in the DB layer somehow….you could provide an argument that specifies the filtering…etc.

      However, all of these either add code smell, are less clear, or just plain won’t work as well (or at all).

      One advantage to this way is if I want to change the information being sent to the backend, I change it at the point where the *info is sent*.

      Let’s say there are two places in the codebase where I do the above:

      foo = ->
      # …stuff
      (sendToAnalyticsDB “users”, { age, name, location } for { age, name, location } in users)

      bar = ->
      # …stuff
      (sendToAnalyticsDB “users”, { age, name, location } for { age, name, location } in users)

      What if I want bar to send slightly different information? If I pushed the filtering/constructing logic to somewhere else, this could be a mess. With destructing it’s easy as:

      bar = ->
      # …stuff
      (sendToAnalyticsDB “users”, { age, name, location, fooFlag: true } for { age, name, location } in users)

      So now that we’ve established that this syntax is very useful and more powerful than some other options, we’re left with your suggestion that it is somehow “hard to read” or “hard to understand”…a “code smell”.

      I’m afraid in this case it would purely be “brain smell”.

      The destructuring syntax used here is *very* simple and straightforward. There’s no ambiguity as to what it means, and anyone who knows CoffeeScript will understand right away what it means.

      What makes Perl unreadable is things like magic variables, where $* has some magic meaning….and the fact that syntax does different things in different cases, etc etc. A single line of Perl can be trying to convey a staggering amount of information.

      To compare *extremely simple* destructuring to that is purely laughable.

      The syntax is simple, and more importantly *consistent*….it’s object literal syntax with some added features/behavior that let you use it on the *other* side of assignment.

      So essentially what we’re left with is the argument “Well people who don’t know CoffeeScript or have some kind of brain defect that prevents them from understanding incredibly simple constructs won’t understand it”….and that’s a ludicrous argument. By that rationale we can’t use *any* programming language because some people don’t know it…and if we do we have to restrict ourselves to only the most basic parts of the language so as to confuse no-one. Sorry Haskellers, no more pattern matching…only if/elses because everyone will understand that.

      No, if you’re going to criticize a language feature/syntax….you have to have an *objective* and *rational* reason for it… “it makes my brain hurt” does not count. Look at the excellent work of Douglas Crockford…for each feature he tells you not to use, he gives clear and fairly objective reasons as to why it’s problematic.

      For example, with a switch statement, it’s very easy to forget the “break”, causing logic errors. Or “with” is slow and causes ambiguity. Furthermore, all of these are weighed against the benefits (or lack thereof) of the feature.

      Desturcturing in Coffeescript is simple and straight forward. The syntax is clear, again it’s *consistent* with objects in the rest of the language. It doesn’t introduce any more potential for errors than any other construct. It has one simple idea to communicate, and it does that with minimal syntax.

      { age, name } = foo

      Can and does mean only one thing. The only possible problem is that one doesn’t understand destructuring, and again…that’s a problem with the brain not the code/langauge.

      More importantly, I’d say in comprehensions the destructuring version is *easier* to read:

      age for { age } in users

      Here the brackets really really stand out in the code. It makes it obvious that something special is going on. This is opposed to the other version where everything is smooshed together and it’s not necessarily clear you are accessing a property of each item in users in turn.

      ( In reality I think the difference in readability is negligible, but the point is I don’t think the readability argument has much credibility. )

      If you want to pull it out into some more verbose version:

      for user in users
      sendToAnalyticsDB “users”,
      {
      age: user.age,
      name: user.name,
      location: user.location
      }

      You could. If you find that more readable, that’s totally valid too. I just think that you’d be hard pressed to make a fairly objective point as to why that is superior in general and not a decision you made based on one’s/one’s team’s preferences or shortcomings.

      It just annoys me that every time I read something about Coffeescript, someone chimes in with something like “Coffeescript syntax is hard to read, will confuse noobs because people who don’t know it can’t read it”.

      That is the same as saying “The problem with Spanish is that people who can’t read Spanish can’t read it.” ….which is an argument so devoid of logic, sense, or practical implications that I find it extremely offensive and worrisome.

      • Null

        I also want to add that a lot of people don’t appreciate the value of conciseness. Conciseness *matters*. Obviously there is a limit, and you do have a point that one must constantly find balance.

        However, when you are writing a program you are trying to accomplish something. THAT is what is important; your goal.

        One of the thing’s that CoffeeScript does that is great is it takes things that are *CONCEPTUALLY SIMPLE* but in JavaScript are *SYNTACTICALLY VERBOSE* and makes those ends match up.

        What matters in my programs is the *business logic*. Something like “switch these values around” is a very simple idea, and I should be able to do that in one line.

        [foo, bar] = [bar, foo]

        Now this trifle is pushed to the background and I can focus on what’s actually important…the ideas I really want to express. Now I can write my ideas in code that more closely matches how I actually think them…making them not only easier to put down, but easier to read, and easier to spot problems in.

        • Ben

          I completely agree with everything you just said.

      • Ben

        Excellent points Null. While I do agree with you that I’m coming from a place of brain inflexibility and I could certainly climb the learning curve, I don’t think you are completely correct.

        1. As mature, talented software engineers (which I gather you are), we don’t write code for computers to understand, we write code for other developers to understand. This is so that they can maintain it when we move on to bigger and brighter things. Which is why clean, elegant code that is easy for people to grok is paramount.

        2. Compare these two blocks of code:

        (sendToAnalyticsDB "users", { age, name, location } for { age, name, location } in users)

        to this

        users.forEach( function( user ) {
        sendToAnalyticsDB("users", { age : user.age, name : user.name, location : user.location });

        Which one is clearer to you?

      • shawnfumo

        I agree, but just to throw another option out there for the simple case of grabbing top-level properties of an object (if you are already using underscore/lowdash):

        (sendToAnalyticsDB “users”, _.pick(user, ‘age’, ‘name’, ‘location’) for user in users)

        The comprehension is certainly more flexible though, since you could do something like create a fullName property on the resulting object in a pretty clear way:

        fullNames = (“#{lastName}, #{firstName}” for {firstName, lastName} in people)

  • Pingback: Destructuring CoffeeScript one sip at a time | MuleSoft Blog