I have the following trait and class (actually this is a simplification, the real code is written in Java and is outside of my control):
trait BusinessTermValue {
def getValue: Any
}
class BusinessTermValueImpl(override val getValue: Any) extends BusinessTermValue
Now I'm trying to improve my API without touching the original code (Pimp My Library pattern):
package object businessterms {
implicit final class BusinessTermValueSupport(val btv: BusinessTermValue) extends AnyVal {
def isDefined(): Boolean = btv != null && btv.value != null
def isEmpty(): Boolean = isDefined() && (btv.value match {
case s: String => s.isEmpty
case l: Traversable[_] => l.isEmpty
case c: java.util.Collection[_] => c.isEmpty
case _ => false
})
def getAs[T](): T = btv.value.asInstanceOf[T]
}
object BusinessTerm {
def apply(value: Any): BusinessTermValue = new BusinessTermValueImpl(value)
}
}
It works quite well:
println(BusinessTerm("A String").isEmpty) // false
println(BusinessTerm(1).isEmpty) // false, Int can't be empty
println(BusinessTerm(new Integer(1)).isEmpty) // false, Integer can't be empty
println(BusinessTerm(List(1, 2, 3)).isEmpty) // false
println(BusinessTerm(List(1, 2, 3).asJava).isEmpty) // false
println(BusinessTerm("").isEmpty) // true
println(BusinessTerm(List()).isEmpty) // true
println(BusinessTerm(Seq()).isEmpty) // true
println(BusinessTerm(Map()).isEmpty) // true
println(BusinessTerm(List().asJava).isEmpty) // true
Still the pattern matching in isEmpty
is cumbersome. Ideally I would like to use structural types and make sure that any type that implements isEmpty
works with my API.
Unfortunately the code below doesn't work. The variable e
matches any type, even when value
does not define isEmpty
:
def isEmpty(): Boolean = isDefined() && (btv.value match {
case e: { def isEmpty(): Boolean } => e.isEmpty
case _ => false
})
Is there a way to delegate isEmpty
only when the underlying value
implements it?
I was the one suggesting the Try version
def isEmpty(): Boolean = isDefined && (btv.value match {
case e: { def isEmpty(): Boolean } => Try(e.isEmpty).getOrElse(false)
case _ => false
})
I also noted that catching exceptions is expensive but I believe that this case would be the least common as in principle you would be using this construct when you expect to have an isEmpty method. So I think the trade-off may pay and would consider this a case of defensive coding.