1

I have created a class called CaseInsensitive which wraps a string (see 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))
  }
}
Community
  • 1
  • 1
waterlooalex
  • 13,642
  • 16
  • 78
  • 99
  • 2
    can't you just implement and use a CI comparision? I believe a CI string is an ill-formed concept, case sensitivity is a property of the operation, not of the data. What happens if you compare a CSstring with CIstring? These two are about C++, but esp. the Austern's piece has some relevant thoughts. http://www.gotw.ca/gotw/029.htm http://lafstern.org/matt/col2_new.pdf – just somebody Nov 30 '09 at 16:28
  • I'm storing objects in HashMap[T] and other data structures, how would I "just implement a CI comparison"? Regardless, this question is not specific to this class CaseInsensitive, but is about Scala extractors in general. – waterlooalex Nov 30 '09 at 16:34
  • Thanks for the links to Austern's piece, just trying to figure out how to apply his ideas in Scala. – waterlooalex Nov 30 '09 at 16:42
  • An extractor "unbuilds" a data structure. You'd need an implicit *from* `CaseInsensitive` *to* `String` for that match to work. Not that I think it would work, but just to correct the misunderstanding. – Daniel C. Sobral Dec 01 '09 at 10:34
  • @Daniel: I have an implicit for both directions. I think it could go either way, couldn't it? E.g. unbuild the PropertyKey to a CaseInsensitive, then implicitly convert that to string, then compare that against the provided string. Or, unbuild the PropertyKey to a CaseInsensitive, then implicitly convert the provided string to a CaseInsensitive, then compare them. – waterlooalex Dec 01 '09 at 13:51

1 Answers1

4

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
  • 13,186
  • 3
  • 44
  • 32
  • 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! – waterlooalex Nov 30 '09 at 18:53
  • 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. – waterlooalex Nov 30 '09 at 18:55
  • I guess trying to pattern match in this way made me think this way. Will edit answer to clarify (with code). – Mitch Blevins Nov 30 '09 at 19:07
  • +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 Nov 30 '09 at 19:28