scalaanorm

What does the tilde (~) mean in this Scala example?


http://woss.name/2012/04/02/retrieving-bigdecimals-from-a-database-with-anorm-scala/

object Site {
  val allFieldsParser = {
    get[Pk[Long]]("sites.id") ~     // Help me parse this syntax
    get[String]("sites.name") ~
    get[BigDecimal]("sites.latitude") ~
    get[BigDecimal]("sites.longitude") map {
      case id ~ name ~ latitude ~ longitude =>
        Site(id, name, latitude, longitude)
    }
  }

  def findAll(): Seq[Site] = {
    DB.withConnection { implicit connection =>
      SQL("SELECT * FROM sites").as(Site.allFieldsParser *)
    }
  }
}

Solution

  • In your example, ~ is being used in two different ways to mean two different things. In the first part you have

    get[Pk[Long]]("sites.id") ~     // Help me parse this syntax
    get[String]("sites.name") ~
    get[BigDecimal]("sites.latitude") ~
    

    etc. As it has already been pointed out, this is just method invocation, it is the same as

    get[Pk[Long]]("sites.id").~(get[String]("sites.name").~(...
    

    You can look at the definition of this method in the anorm source. It is a method on a RowParser[A] (a parser that parses an A, which takes a RowParser[B] (a parser that parses a B) and returns a parser that parses a A ~ B. This A ~ B is a second meaning for ~. This is now referring not to a method, but to a case class defined in the same file here.

    case class ~[+A, +B](_1: A, _2: B)
    

    This is just a idiosyncratic way of referring to a class ~[A,B]. At the type level, when you have a two argument type constructor, you can use the name of the class in infix notation. This isn't anything special about ~, it would work with any two argument type constructor. If you had trait Foo[A,B] you could refer to that as A Foo B. Analogously, in pattern matching, variables a and b can be bound using the syntax a Foo b, which is referred to as an Infix Operation Pattern in section 8.1.10 of the language specification.

    In the second part of your example you have:

    case id ~ name ~ latitude ~ longitude =>
    

    This is pattern matching on these ~ case clases which are the result of running the parse you constructed above. So this is really just a nicer way of writing:

    case ~(~(~(id, name), latitude), longitude) =>