npiv
npiv

Reputation: 1837

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 {
  def main(args:List[String]) {
    val boss = People.all.find { _.isInstanceOf [Authority] }.get
    boss.giveOrder // This line doesnt compile
  }
}

Upvotes: 3

Views: 400

Answers (4)

Jean-Philippe Pellet
Jean-Philippe Pellet

Reputation: 59994

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.

Upvotes: 7

Kevin Wright
Kevin Wright

Reputation: 49705

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
}

Upvotes: 5

michael.kebe
michael.kebe

Reputation: 11085

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)

Upvotes: 0

Jus12
Jus12

Reputation: 18024

Try this

boss.asInstanceOf[Authority].giveOrder

or this

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

Upvotes: 0

Related Questions