Search code examples
scalatwirl

Simplify/DRY up a case statement in Scala for Twirl Templates


So I'm using play Twirl templates (not within play; independent project) and I have some templates that generate some database DDLs. The following works:

if(config.params.showDDL.isSupplied) {
    print( BigSenseServer.config.options("dbms") match {
      case "mysql" => txt.mysql(
        BigSenseServer.config.options("dbDatabase"),
        InetAddress.getLocalHost().getCanonicalHostName,
        BigSenseServer.config.options("dboUser"),
        BigSenseServer.config.options("dboPass"),
        BigSenseServer.config.options("dbUser"),
        BigSenseServer.config.options("dbPass")
      )
      case "pgsql" => txt.pgsql(
        BigSenseServer.config.options("dbDatabase"),
        InetAddress.getLocalHost().getCanonicalHostName,
        BigSenseServer.config.options("dboUser"),
        BigSenseServer.config.options("dboPass"),
        BigSenseServer.config.options("dbUser"),
        BigSenseServer.config.options("dbPass")
      )
      case "mssql" => txt.mssql$.MODULE$(
        BigSenseServer.config.options("dbDatabase"),
        InetAddress.getLocalHost().getCanonicalHostName,
        BigSenseServer.config.options("dboUser"),
        BigSenseServer.config.options("dboPass"),
        BigSenseServer.config.options("dbUser"),
        BigSenseServer.config.options("dbPass")
      )
    })

    System.exit(0)
}

But I have a lot of repeated statements. If I try to assign the case to a variable and use the $.MODULE$ trick, I get an error saying my variable doesn't take parameters:

val b = BigSenseServer.config.options("dbms") match {
      case "mysql" => txt.mysql$.MODULE$
      case "pgsql" => txt.pgsql$.MODULE$
      case "mssql" => txt.mssql$.MODULE$
}
b("string1","string2","string3","string4","string5","string6")

and the error:

BigSense/src/main/scala/io/bigsense/server/BigSenseServer.scala:32: play.twirl.api.BaseScalaTemplate[T,F] with play.twirl.api.Template6[A,B,C,D,E,F,Result] does not take parameters

What's the best way to simplify this Scala code?

EDIT: Final Solution using a combination of the answers below

The answers below suggest creating factory classes, but I really want to avoid that since I already have the Twirl generated template object. The partially applied functions gave me a better understanding of how to achieve this. Turns out all I needed to do was to pick the apply methods and to eta-expand these; if necessary in combination with partial function application. The following works great:

  if(config.params.showDDL.isSupplied) {

    print((config.options("dbms") match {
      case "pgsql" => 
        txt.pgsql.apply _
      case "mssql" => 
        txt.mssql.apply _
      case "mysql" => 
        txt.mysql.apply(InetAddress.getLocalHost().getCanonicalHostName,
                        _:String, _:String, _:String,_:String, _:String)
    })(
        config.options("dbDatabase"),
        config.options("dboUser"),
        config.options("dboPass"),
        config.options("dbUser"),
        config.options("dbPass")
    ))

    System.exit(0)
  }

Solution

  • You can try to use eta-expansion and partially applied functions.

    Given a factory with some methods:

    class Factory {
      def mysql(i: Int, s: String) = s"x: $i/$s"
      def pgsql(i: Int, s: String) = s"y: $i/$s"
      def mssql(i: Int, j: Int, s: String) = s"z: $i/$j/$s"
    }
    

    You can abstract over the methods like this:

    val factory = new Factory()
    
    // Arguments required by all factory methods
    val i = 5
    val s = "Hello"
    
    Seq("mysql", "pgsql", "mssql").foreach {
      name =>
    
        val f = name match {
          case "mysql" =>
            // Eta-expand: Convert method into function
            factory.mysql _
    
          case "pgsql" =>
            factory.pgsql _
    
          case "mssql" =>
            // Argument for only one factory method
            val j = 10
            // Eta-expand, then apply function partially
            factory.mssql(_ :Int, j, _: String)
        }
    
        // Fill in common arguments into the new function
        val result = f(i, s)
        println(name + " -> " + result)
    }
    

    As you can see in the "mssql" case, the arguments may even differ; yet the common arguments only need to be passed once. The foreach loop is just to test each case, the code in the body shows how to partially apply a function.