开发者

How to get the proper return type when using a filter based on type in Scala

The following doesn't compile. Do I need to cast the person first?

 object People {
  def all = List(
    new Person("Jack", 33),
    new Person("John", 31) with Authority,
    new Person("Jill", 21),
    new Person("Mark", 43)
  )
}

class Person(val name: String, val age: Int) 

trait Authority {
  def giveOrder {
    println("do your work!")
  }
}

object Runner 开发者_Python百科{
  def main(args:List[String]) {
    val boss = People.all.find { _.isInstanceOf [Authority] }.get
    boss.giveOrder // This line doesnt compile
  }
}


You're right thinking that somehow, there should be a mechanism that lets you avoid casting. Such a cast would be ugly and redundant, as it already appears in the filter anyway. find, however, does not care at all about the shape of the predicate it gets; it just applies it and returns an Option[A] if A is the static type of the collection's elements.

What you need is the collect function:

val boss = People.all.collect { case boss: Authority => boss }.head

collect creates a new collection. If you want to avoid this (if you're really only interested in the first element which is of kind Authority) in case of a potentially very long list of potential bosses, you may want to switch to a view to have it evaluated lazily:

val boss = People.all.view.collect { case boss: Authority => boss }.head

Finally, unless you're absolutely sure that there is always at least one boss in your list, you should really test whether or not the search was successful, e.g. like this:

val bossOpt = People.all.view.collect { case boss: Authority => boss }.headOption
bossOpt.foreach(_.giveOrder) // happens only if a boss was found

Edit: Finally, if you're using Scala 2.9, you should definitely use collectFirst as explained in Kevin Wright's answer.


Jean-Philippe's answer is good, but it's possible to go one step further...

If using Scala 2.9, you'll also have a collectFirst method available, allowing you to avoid all those tedious view's, head's and headOption's

val boss = People.all.collectFirst { case x: Authority => x }
boss.foreach(_.giveOrder) // happens only if a boss was found

boss is still an Option[Person], I recommend that you keep it this way for the sake of safer code. If you want, you can also use a for-comprehension, which some people to be cleaner still:

for(boss <- People.all.collectFirst { case x: Authority => x }) {
  boss.giveOrder // happens only if a boss was found
}


Try this

boss.asInstanceOf[Authority].giveOrder

or this

val boss =  People.all.find { _.isInstanceOf [Authority] }.get.asInstanceOf[Person with Authority]


Do you really want to find just the first one? find does exactly this. Consider using the solution from Jean-Philippe if you want to find all Authoritys:

val authorities = People.all.collect {
  case boss: Authority => boss
}.foreach(_.giveOrder)
0

上一篇:

下一篇:

精彩评论

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

最新问答

问答排行榜