String pattern matching best practice

35,735

Solution 1

In terms of syntax, you can modify just a tiny bit you case statements:

case url if url.startsWith("jdbc:mysql:") => "com.mysql.jdbc.Driver"

This simply binds the value url to the pattern expression (which is also url) and adds a guard expression with a test. That should make the code compile.

To make it a little bit more scala-like, you can return an Option[String] (I removed a couple clause since it's just for illustration):

def resolveDriver(url: String) = url match {
  case u if u.startsWith("jdbc:mysql:") => Some("com.mysql.jdbc.Driver")
  case u if u.startsWith("jdbc:postgresql:") => Some("org.postgresql.Driver")
  case _ => None
}

That is unless you want to manage exceptions.

Solution 2

Here is an alternate way. Store all the mappings in a map and then use collectFirst method to find the match. Type signature of collectFirst is:

def TraversableOnce[A].collectFirst[B](pf: PartialFunction[A, B]): Option[B]

Usage:

scala> val urlMappings = Map("jdbc:mysql:" -> "com.mysql.jdbc.Driver", "jdbc:postgresql:" -> "org.postgresql.Driver")
urlMappings: scala.collection.immutable.Map[java.lang.String,java.lang.String] = Map(jdbc:mysql: -> com.mysql.jdbc.Drive
r, jdbc:postgresql: -> org.postgresql.Driver)

scala> val url = "jdbc:mysql:somestuff"
url: java.lang.String = jdbc:mysql:somestuff

scala> urlMappings collectFirst { case(k, v) if url startsWith k => v }
res1: Option[java.lang.String] = Some(com.mysql.jdbc.Driver)

Solution 3

Starting Scala 2.13, it's possible to pattern match a Strings by unapplying a string interpolator:

val s"jdbc:$dialect:$rest" = "jdbc:mysql:whatever"
// dialect: String = "mysql"
// rest: String = "whatever"

Then in our case, it's just a matter of mapping the extracted value (the sql dialect) to the appropriate driver using a Map:

val drivers = Map(
  "postgresql" -> "org.postgresql.Driver",
  "mysql"      -> "com.mysql.jdbc.Driver",
  "h2"         -> "org.h2.Driver"
)
val driver = drivers(dialect)
// driver: String = "com.mysql.jdbc.Driver"

If you are expecting malformed inputs, you can also use a match statement:

"jdbc:postgresql:something" match {
  case s"jdbc:$dialect:$rest" => Some(dialect)
  case _                      => None
}
// Option[String] = Some("postgresql")
Share:
35,735
Nikita Volkov
Author by

Nikita Volkov

Updated on July 24, 2020

Comments

  • Nikita Volkov
    Nikita Volkov almost 4 years

    Following is the code that doesn't work but it describes what I want to do.

    Could you please recommend the best approach to this problem?

    def resolveDriver(url: String) = {
      url match {
        case url.startsWith("jdbc:mysql:") => "com.mysql.jdbc.Driver"
        case url.startsWith("jdbc:postgresql:") => "org.postgresql.Driver"
        case url.startsWith("jdbc:h2:") => "org.h2.Driver"
        case url.startsWith("jdbc:hsqldb:") => "org.hsqldb.jdbcDriver"
        case _ => throw new IllegalArgumentException
      }
    }
    
  • Nikita Volkov
    Nikita Volkov over 12 years
    Thanks! That's exactly what I was looking for! I'm glad I asked the question 'cuz I was already preparing myself to create a case class for that, which smelled like an overcomplication. Also I thank you for correcting me on the Exception throwing.
  • Nikita Volkov
    Nikita Volkov over 12 years
    Thanks but isn't what you propose an abstraction over match?
  • missingfaktor
    missingfaktor over 12 years
    @mojojojo: Not quite. The set of case expressions that follows match constitutes a PartialFunction. collectFirst is a method that accepts a PartialFunction, loops over the collection, and returns the first match found as wrapped in Some. (returns None if no match found.)
  • jwvh
    jwvh almost 5 years
    Code-only answers aren't as useful as code-with-commentary, especially with questions this old (over 7 year) it is useful to point out how your answer differs from all the previous answers.
  • Volty De Qua
    Volty De Qua almost 5 years
    Sorry, but if others are supposed to (also) read the previous answers, then the code I posted is self-evident - map driven on pertinent parts, with full pattern match capturing dbms part (and eventually else), then partial function that saves us the stating of None. And all this taking into account the the initial question and code. I do it this way. Whether it's better or not, whether it differs enough from others', is just an opinion. Thanks.
  • Volty De Qua
    Volty De Qua almost 5 years
    Sorry to flood, but I do not understand your point about the age of this question. How / why the age matters? Fresh questions need less elaboration? Aren't this kind of questions ageless?
  • jwvh
    jwvh almost 5 years
    5-It's not uncommon (far too common) for newcomers to encounter an old question and answer it without reading the previous answers. Commentary accompanying your code can make it clear that you're not in that category.
  • niid
    niid over 3 years
    So I take it that the matching of string interpolators also only works in >=2.13?