Destructuring Assignment in CoffeeScript

Posted on by in Development

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.