mapcan in groovy

Groovy has borrowed most of the good stuff from functional and higher-order languages, but one operation I miss is mapcan. mapcan comes from lisp and while it’s a bit hard to describe, it ends up being useful in a lot of situations. It’s very similar to mapcar, except instead of transforming one element to one element, it transforms one element to zero or more elements. In groovy terms, it’s like collect where collect’s closure could return a list of elements instead of just one. It a way, it’s kind of like a combination of collect and findAll on steroids.

In lisp, it looks like this:

(defvar *interface-methods*
  '((:Serializable . ())
    (:Comparable . (:compareTo))
    (:Iterator . (:hasNext :next :remove))))

(print (mapcan #'cdr *interface-methods*))
;; evaluates to '(:compareTo :hasNext :next :remove)

A first cut in groovy might look like:

Collection.metaClass.mapcan = { cl -> 
    delegate.collect{cl(it)}.flatten() 
}

The problem with that implementation is that flatten is recursive and will flatten nested lists too. He’s another approach with inject:

Collection.metaClass.mapcan = { cl ->
    delegate.inject([]) {collection, it -> collection + cl(it)}
}

The mapcan name doesn’t really fit with groovy, so I renamed it collectn instead. Here are some examples of its use:

Collection.metaClass.collectn = { cl ->
    delegate.inject([]) {collection, it -> collection + cl(it)}
}

def s = ["", "zero", "or more", "words per string"]
assert s.collectn { it.tokenize() }.sort() ==
    ["zero", "or", "more", "words", "per", "string"].sort()

Map.metaClass.collectn = { cl ->
    delegate.inject([]) {collection, it -> collection + cl(it)}
}

def interfaceMethods = [
    Serializable: [],
    Comparable: ['compareTo'],
    Iterator: ['hasNext', 'next', 'remove']
]
assert interfaceMethods.collectn { it.value }.sort() == 
    ['compareTo', 'hasNext', 'next', 'remove'].sort()

Comments are closed.