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 Authority
s:
val authorities = People.all.collect {
case boss: Authority => boss
}.foreach(_.giveOrder)
精彩评论