1

I have a class Foo extends Bar and a List or other collection of base class:

val bars: Iterable[Bar] 

I need to extract all Foo elements from the collection. The following is the code:

val fooes: Iterable[Foo] = bars
    .filter(x => Try(x.isInstanceOf[Foo]).isSuccess))
    .map(_.isInstanceOf[Foo])

Is there conciser approach?

Loom
  • 9,768
  • 22
  • 60
  • 112

2 Answers2

7
val fooes: Iterable[Foo] = bars.collect{case foo:Foo => foo}

The .collect() method takes a partial-function as its parameter. In this case the function is defined only for Foo types. All others are ignored.

jwvh
  • 50,871
  • 7
  • 38
  • 64
2

Couple of possible rewrites worth remembering in general

  • filter followed by map as collect
  • isInstanceOf followed by asInstanceOf as pattern match with typed pattern

Hence the following discouraged style

bars
  .filter { _.isInstanceOf[Foo] }
  .map    { _.asInstanceOf[Foo] }

can be rewritten to idiomatic style

bars collect { case foo: Foo => foo }

...writing type tests and casts is rather verbose in Scala. That's intentional, because it is not encouraged practice. You are usually better off using a pattern match with a typed pattern. That's particularly true if you need to do both a type test and a type cast, because both operations are then rolled into a single pattern match.

Note the nature of typed pattern is still just runtime type check followed by runtime type cast, that is, it merely represent nicer stylistic clothing not an increase in type safety. For example

scala -print -e 'lazy val result: String = (42: Any) match { case v: String => v }'

expands to something like

<synthetic> val x1: Object = scala.Int.box(42);
if (x1.$isInstanceOf[String]()) {
  <synthetic> val x2: String = (x1.$asInstanceOf[String]());
  ...
}

where we clearly see type check isInstanceOf followed by type cast asInstanceOf.

Mario Galic
  • 47,285
  • 6
  • 56
  • 98