Groovy Switch Case with Closures

Intro

Yesterday I was talking to my dear friend @ilopmar about his Greach presentation about Javaslang. It was when we were comparing Javaslang’s pattern matching module vs Groovy’s switch case when my Groovy instinct made me realise how underated Groovy’s switch-case statement is, specially how closures can be applied to Groovy’s switch/case statements to create limited pattern-matching expressions.

BTW, Don’t get me wrong I think Javaslang is an A-M-A-Z-I-N-G library, and I’ve been using it in my projects since the very beginning. This entry is just a claim to use the full potential of Groovy’s switch case statement.

Let me show you my point. Lets say we have the Person type:

Person
package pm

import groovy.transform.Canonical
import groovy.transform.TupleConstructor

@Canonical
@TupleConstructor
class Person {
    String name
    Integer age
}

The next example shows how to return whether john or carl depending on the exact values of the properties contained in the Person object passed as parameter.

Initial case
String example0(Person person) {
    if (person.name == 'carl' && person.age == 22) {
        return 'carl'
    }

    if (person.name == 'john' && person.age == 34) {
        return 'john'
    }

    return 'nobody'
}

We all agree it’s a very verbose syntax, and bearing in mind languages such as Haskell or Scala have already fixed this problem using pattern matching, why should we suffering this type of syntax ?

Although we don’t have a Scala-like pattern matching…​ yet, we are clearly underestimating the power of Groovy’s switch/case statement.

Exact Matching

When trying to figure out which action to take based on properties of the object passed as parameter, we can trust object identity by creating an object with the same values.

Exact matching
String example1a(Person person) {
    switch (person) {
        case new Person('carl', 22): return 'carl'
        case new Person('john', 34): return 'john'

        default:
        return 'nobody'
    }
}

It’s true that, by default, we can’t omit the new when creating a new object instance, but, that’s just an excuse, we can always create a method with the class name that builds the instance

Tuple Constructor without new
static Person Person(String name, Integer age) {
    new Person(name, age)
}

And then come again with this:

Exact matching
String example1(Person person) {
    switch (person) {
        case Person('carl', 22): return 'carl'
        case Person('john', 34): return 'john'

        default:
        return 'nobody'
    }
}

No magic here, this is just old plain java rules: equals and hashcode underneath applied to out class thanks to the @Canonical transformation. But what happens when we would like to get fancy and match using certain rules over the object’s properties ?

Pattern matching with closures

In the previous example we were comparing values against values, but what if we would like to compare values against certain rules or patterns ? Now what we want is, given a data structure (an object here), to check if its internal values match a given set of patterns.

With that in mind lets get back to the Person example to see if we can apply certain patterns to the object’s properties:

Pattern matching
String example2(Person person) {
    switch (person) {
        case Person(endsWith('arl'), gt(25)): return 'carl' (1)
        case Person(endsWith('hn'), lt(23)):  return 'john' (2)
        case Person(any(), gt(60)):           return 'maria' (3)

        default:
        return 'nobody' (4)
    }
}
1 Matches any person whose name ends with arl and having an age greater than 25
2 Matches any person whose name ends with hn and having an age less than 23
3 Matches any person with any name and having an age greater than 60
4 In any other case return nobody

Now, How is this working ? Well actually Person(pattern, pattern) is a method returning a Closure acting as a predicate:

Person w Closure
static Closure<Boolean> Person(Closure<Boolean> name, Closure<Boolean> age) {
    return { Person p ->
        name(p.name) && age(p.age)
    }
}

Eventually it will take the person instance to evaluate and it will execute both patterns with the same value and if both executions are correct then case expression will succeed.

Let me show you how I built the patterns any, gt, lt, and endsWith:

any
static Closure<Boolean> any() {
    return { -> true }
}
endsWith
static Closure<Boolean> endsWith(String ending) {
    return { String s ->
        s.endsWith(ending)
    }
}
gt
static Closure<Boolean> gt(Integer lowerBound) {
    return { Integer n -> n > lowerBound }
}
lt
static Closure<Boolean> lt(Integer upperBound) {
    return { Integer n -> n < upperBound }
}

Altogether

Apart from using closures you can also use classes to check whether the instance evaluated in the case case is an instance of that class or not.

In next example we have the following domain classes:

Mammal
package pm

import groovy.transform.Canonical

@Canonical
class Mammal {
    String name
    Integer age
}

Mammal uses @Canonical which among other things implements equals and hashCode methods. And it also uses @TupleConstructor to avoid using a constructor map.

Dog
package pm

import groovy.transform.InheritConstructors

@InheritConstructors
class Dog extends Mammal { }

Dog inherits Mammal constructors.

Cat
package pm

import groovy.transform.InheritConstructors

@InheritConstructors
class Cat extends Mammal { }

Cat also inherits Mammal constructors.

Then we would like to be able to match:

  • Types: The evaluated parameter is of certain type (a Dog, or a Cat, or a Mammal)

  • Values: The evaluated parameter is a certain value (properties are equal)

  • Patterns: The evaluated parameter follows certain pattern rules

Having that in mind I came up with the following code:

Pattern matching
static String check(Mammal mammal) {
    switch (mammal) {
        case Dog:                return 'I dont like dogs'         (1)
        case Cat('jonas', 4):    return 'jonas cat is still young' (2)
        case Cat(any(), gt(10)): return 'it should be rocky cat'   (3)
        case Cat:                return 'at least is a cat'        (4)

        default:
        return 'no idea whatsoever'
    }
}
1 Matches if it is any Dog
2 Matches if it is a specific Cat
3 Matches if it is a Cat following certain patterns
4 Matches if it is any Cat

A more advance pattern matching in Groovy ?

As I said at the beggining, Groovy’s switch/case is not a full pattern matching solution. I would point out at least the fact that it doesn’t have value destructuring, which is very useful in data structures such as lists.

The good news is that Sergei Egorov wrote some years ago a library bringing pattern matching to Groovy. I strongly recommend to check it out. These are some examples of what can be achieved with this library: