views:

283

answers:

1

I have created a class called CaseInsensitive which wraps a string (see http://stackoverflow.com/questions/1745910/implementing-a-string-class-that-does-case-insensitive-comparisions-in-scala).

I've created a case class which has a member variable of type CaseInsensitive, so it gets a default unapply method, which extracts a variable of type CaseInsensitive, but I was hoping to use it like this:

case class PropertyKey( val name : CaseInsensitive )

val foo = new PropertyKey("foo")
val result = foo match {
  case PropertyKey("foo") => true
  case _ => false
}

This code fails to compile: (on the extractor line, not the constructor line)

type mismatch;
 found   : java.lang.String("foo")
 required: com.acme.CaseInsensitive

But I thought my implicit conversions from String to CaseInsensitive would enable this to compile, rather than me having to type the more verbose:

case class PropertyKey( val name : CaseInsensitive )

val foo = new PropertyKey("foo")
val result = foo match {
  case PropertyKey(CaseInsensitive("foo")) => true
  case _ => false
}

Here is the implementation of CaseInsensitive:

/** Used to enable us to easily index objects by string, case insensitive
 * 
 * Note: this class preserve the case of your string!
 */
case class CaseInsensitive ( val _s : String ) extends Proxy {
  require( _s != null)

  val self = _s.toLowerCase
  override def toString = _s

  def i = this // convenience implicit conversion
}

object CaseInsensitive {
  implicit def CaseInsensitive2String(c : CaseInsensitive) = if ( c == null ) null else c._s
  implicit def StringToCaseInsensitive(s : String) = CaseInsensitive(s)

  def fromString( s : String ) = s match {
    case null => None
    case _ => Some(CaseInsensitive(s))
  }
}
+4  A: 

You could always define a convenience extractor and import it (feel free to use a shorter name):

object PropertyKeyCI {
  def unapply(p: PropertyKey): Option[String] = Some(p.name.self)
}

Then, extraction is:

  val foo = new PropertyKey("foo")
  val result = foo match {
    case PropertyKeyCI("foo") => true
    case _ => false
  }


(Bad Semantics Alert)

Although note that this would match as false for PropertyKeyCI("Foo"), because your "CaseInsensitive" class is really a "LowerCase" class. I say this because it is hard for me to imagine what the desired behavior would be for the unapply() method otherwise. From your case class default, you are returning an Option[String] of the original (unlowercased) string, which gives this unintuitive behavior:

  // result == false !!!!
  val foo = new CaseInsensitive("Foo")
  val result = foo match {
    case CaseInsensitive("foo") => true
    case _ => false
  }
  val CaseInsensitive(s) = "aBcDeF"
  assertFalse(s == "abcdef")

Awwww.... toss it. Just use DOS.

Mitch Blevins
Thanks Mitch, yeah I explored that route, its just not as nice/clean. It would be nice if I could just have two unapply methods on the same object!
Alex Black
Are you sure CaseInsensitive is a lower case class? It keeps the existing case, and uses the lowerCase version of the string for hashCode and equals.
Alex Black
I guess trying to pattern match in this way made me think this way. Will edit answer to clarify (with code).
Mitch Blevins
+1 on having multiple unapply methods on the same object. I don't know how many times I've tried that and the compiler keeps rebuffing me.
Mitch Blevins