Any clean way to combine find and instanceof in Scala?
I want to find in some Iterable some elements that both conform to some given type, and validates a predicate taking that type as an argument.
I wrote this method using imperative-style programming, which seems to conform to my expectations. Is there some way to write this in a more "scalaesque" way?
def findMatch[T](it: Iterable[开发者_运维百科_], clazz: Class[T], pred: T => Boolean): Option[T] = {
val itr = it.iterator
var res: Option[T] = None
while (res.isEmpty && itr.hasNext) {
val e = itr.next()
if (clazz.isInstance(e) && pred(clazz.cast(e))) {
res = Some(clazz.cast(e))
}
}
res
}
You can use collect
if you want to find
and then map
.
scala> val it: Iterable[Any] = List(1,2,3,"4")
it: Iterable[Any] = List(1, 2, 3, 4)
scala> it.view.collect{case s: String => s}.headOption
res1: Option[String] = Some(4)
You can work with an existantial type X forSome{typeX}
rather than using _
as type parameter. This then would enable you to write it with the mentioned find method and use the map method on the Option type:
def findMatch[T](it: Iterable[X forSome {type X}], clazz: Class[T], pred: T => Boolean): Option[T] = {
it.find{ e => clazz.isInstance(e) && pred(clazz.cast(e))}.map{clazz.cast(_)}
}
If you divide your problem into subproblems a more idiomatic version is easy to find. You want to
- find all instances of
T
in yourIterable[Any]
- cast them to
T
to make the compiler happy - find the first matching element
For the first point you can easily use the filter
Method on Iterator
. So you have
it.iterator.filter(x => clazz.isInstance(x))
which returns you an Iterator[Any]
that contains only T
s. Now let's convince the compiler:
it.iterator.filter(x => clazz.isInstance(x)).map(x => x.asInstanceOf[T])
Okay, now you have an Iterator[T]
- so you just need to find the first element fulfilling your predicate:
def findMatch[T](it: Iterable[Any], clazz: Class[T], pred: T => Boolean): Option[T] =
it.iterator.filter(x => clazz.isInstance(x))
.map(x => x.asInstanceOf[T])
.find(pred)
You can use Iterable
's find
method and pattern matching with a guard:
scala> val it: Iterable[Any] = List(1,2,3,"4")
it: Iterable[Any] = List(1, 2, 3, 4)
scala> it.find { _ match {
case s: String if s == "4" => true
case _ => false
}}.asInstanceOf[Option[String]]
res0: Option[String] = Some(4)
For an introduction to pattern matching have a look at: http://programming-scala.labs.oreilly.com/ch03.html
精彩评论