开发者

Any clean way to combine find and instanceof in Scala?

开发者 https://www.devze.com 2023-02-25 07:31 出处:网络
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 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

  1. find all instances of T in your Iterable[Any]
  2. cast them to T to make the compiler happy
  3. 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 Ts. 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

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号