Using Regex in a when

In Scala it’s very easy to perform pattern matching on a regex:

val regex = "varchar\\((\\d+)\\)".r
val string = "varchar(255)"

string match {
 case regex(precision) => println(precision)
 case other => println("not a varchar")
}

What’s the closest equivilent in Kotlin? Something like using a regex in a when would be ideal, but if not, then other tips would be good.

3 Likes

I would write

 val regex = Regex("varchar\\((\\d+)\\)")
  val string = "varchar(255)"

  val match = regex.matchEntire(string)
  if (match != null) {
    val (precision) = match.destructured
    println(precision)
  } else {
    println("not a varchar")
  }

If you don’t need “else” branch, it might be shorter:


  regex.matchEntire(string)?.let { match ->
    val (precision) = match.destructured
    println(precision)
  }

Not as short as in Scala, unfortunately.

1 Like

I can of course use an if else branch. I wasn’t asking how to do regex matching. I was wondering if there was a nice easy way of doing it in a when clause like a Scala pattern match. The reason being is I want to do something like

when (str) {
 "a" ->
 "b" -> 
 regex(c) ->
}
2 Likes

There are two forms of when. With when (str) you can only do a direct comparison. And simple when is a replacement for a series of if-s, so it’s not better either. Scala is much more powerful in that aspect, you can’t do unapply in Kotlin, unfortunately. There are some rudimentary forms with destructuring operator, but it can’t be used with when and it is quite limited.

I think this is something that should be considered to be added. Pattern matching like this is one of the best parts of Scala and, imo, would be a welcome addition to Kotlin, in whatever form it would be.

5 Likes
operator fun Regex.contains(text: CharSequence): Boolean = this.matches(text)

when("Alex") {
  in Regex(".+lex") -> { println("ends with lex") }
}
15 Likes

Just to throw in my own hack here:

fun Regex.capture(string: String): List<String>? = find(string)?.destructured?.toList()

inline fun <R> regexWhen(string: String, block: RegexWhen.() -> R): R = RegexWhen(string).block()

inline class RegexWhen(val regexWhenArg: String) {
    inline fun <R: Any> Regex.then(block: (List<String>) -> R): R? = capture(regexWhenArg)?.let(block)
    fun error(): Nothing = error("Unparsed string: $regexWhenArg")
}

(inline class is optional, can be a normal class if your production environment deprecates experimental features)

Usage example:

val cell: String = regexWhen(arg) {
    Regex("""R(\d+)C(\d+)""").then { (r, c) ->
        "RC Cell: $r $c"
    } ?: 
    Regex("""([A-Z]+)(\d+)""").then { (c, r) ->
        "Excel Cell: $c $r"
    } ?: "Unknown"
}

Basically a small DSL. The Elvis operators are a little awkward but necessary for lazy evaluation behavior. Note that if you actually want to return a null value from a “then” block you need to return@regexWhen null to break out of the block.

You can also adapt it to your own use-cases; maybe you’d rather work with the entire MatchResult instead, or do some other kind of pre-processing.

4 Likes

I’ve also come up with something here that you can use like:

whenMatch(line) {
    bookingItemSecondLineRegex {
        // Code
    }

    "Kontoauszug " {
        // Code
    }

    otherwise {
        // Code
    }
}