Search code examples
scalaanorm

Better code layout for Anorm Parsers


I'm using Scala, Anorm, and the Play2 Framework to retrieve data from SQL tables. In many places I have code like this:

val emailViewRecordParser: RowParser[EmailViewRecord] = {
  str( "sfname" ) ~
  str( "slname" ) ~
  str( "practice_name" ) ~
  str( "email" ) ~
  str( "phone_area_code" ) map {
    case
      subscriberFirstName ~
      subscriberLastName ~
      practiceName ~
      subscriberEmail ~
      phoneAreaCode
      deviceAddress =>
      EmailViewRecord(
        subscriberFirstName,
        subscriberLastName,
        practiceName,
        subscriberEmail,
        phoneAreaCode
      )
  }
}

This is difficult to manage, since it's very easy to mess up the parameter ordering/length. Ideally we would be able to write something along the lines of:

val emailViewRecordParser: RowParser[EmailViewRecord] = {
 EmailViewRecord(
  str( "sfname" ) map { subscriberFirstName } subscriberFirstName,
  str( "slname" ) map { subscriberLastName } subscriberLastName,
  str( "practice_name" ) map {  practiceName } practiceName,
  str( "email" ) map { subscriberEmail } subscriberEmail,
  str( "phone_area_code" ) map { phoneAreaCode } phoneAreaCode
 )
}

What's a nice functional way to achieve a layout along those lines? Also, the 'str' function at the beginning could be int, date, etc. so the first function needs to be specific to every line


Solution

  • The problem with line parsers is that they are intended to be used on a whole line. I suggest that your problem is not with that, though, but is with repeatedly giving gigantic names that clutter everything so much that you can't pay attention to the logic any more. Look at this:

    str("sfname") ~ str("slname") ~ str("practice_name") ~
    str("email")  ~ str("phone_area_code") map {
      case a~b~c~d~e => EmailViewRecord(a,b,c,d,e)
    }
    

    It's way harder to get that wrong. If you feel this is inadequately documented, add comments on the parser lines like so:

    val parser =
      str("sfname") ~          // Subscriber first name
      ...
      str("phone_area_code")   // You'll never guess that this is the area code.
    
    parser map { case a~b~c~d~e => EmailViewRecord(a,b,c,d,e) }
    

    There are other possible solutions that involve a lot more tooling, but I'm not sure in your case it's worth the effort.