Destructuring arguments in CoffeeScript

CoffeeScript has a good and fairly undocumented feature — destructuring arguments. It’s very similar to binding forms from clojure and a bit like pattern matching like in scala and erlang. It has syntax similar to destructuring assignment but works with functions (and methods) arguments.

Simple examples:

getUppercaseName = ({name}) -> name.toUpperCase()
getUppercaseName name: 'test'
# 'TEST'

head = ([x, ...]) -> x
head [1, 2, 3, 4]
# 1

tail = ([_, xs...]) -> xs
tail [1, 2, 3, 4]
# [ 2, 3, 4 ]

prettifyUser = ({groups: {names: groupNames}, username}) ->
    "#{username}: #{groupNames.join(', ')}"
    groups: {names: ['a', 'b', 'c']}
    username: 'user'
# 'user: a, b, c'

Looks useful? But will be more if we combine it with underscore.js. Assume we need to transform result of $.serializeArray() like:

    {"name": "title", "value": "Test Title"},
    {"name": "text", "value": ""},
    {"name": "tags", "value": "Python"},
    {"name": "tags", "value": "CoffeeScript"},
    {"name": "tags", "value": "Clojure"},
    {"name": "csrfmiddlewaretoken", "value": "token"}

To something like (fields with blank value and service fields should be removed, values of fields with same name should be presented as a list):

    "title": "Test Title",
    "tags": ["Python", "CoffeeScript", "Clojure"]

Let’s start with imperative solution:

transform = (serializedForm) ->
    groupByName = ->
        result = {}
        for field in serializedForm
            if field.value != '' and != 'csrfmiddlewaretoken'
                if result[]
                    result[].push field.value
                    result[] = [field.value]
    flattenFieldsWithSingleValue = ->
        result = {}
        for name, value in groupByName()
            if value.length == 1
                result[name] = value[0]
                result[name] = value

It works, but looks ugly and complex.

Now look to implementation with underscore.js and destructuring arguments:

transform = (serializedForm) ->
    _.chain serializedForm
     .reject ({name, value}) ->
         value == '' or name == 'csrfmiddlewaretoken'
     .groupBy 'name'
     .map (vals, name) ->
         vals =, ({value}) -> value)
         [name, if vals.length > 1 then vals else vals[0]]

A lot better I think. Less lines of code, each transformation step can be simple separated from each other. And all this because of fluent interface presented by underscore.js _.chain and destructuring arguments.

comments powered by Disqus