8

Is it possible to express the following code in such a way that the map and null skipping is expressed in one call?

list.map(_.accept(this, arg).asInstanceOf[T]).filter(_ != null)
Timo Westkämper
  • 21,824
  • 5
  • 78
  • 111
  • I guess that `_.accept(this, arg)` can return `null`. In this case slightly more idiomatic (although still with two calls): `list.map(i => Option(i.accept(this, arg).asInstanceOf[T])).flatten` – Tomasz Nurkiewicz Jan 20 '12 at 09:14
  • 1
    Please someone correct me if I'm wrong, but isn't `collect` the combination of `map` and `filter`? Or was it the other way around? Or am I just wrong? ;) – agilesteel Jan 20 '12 at 09:33
  • 1
    @agilesteel, yes, collect might work. Could you contribute a code snippet? – Timo Westkämper Jan 20 '12 at 09:35

2 Answers2

11
list flatMap { i => Option(i.accept(this, arg).asInstanceOf[T]) }

or alternatively, if you like, (though this will be converted more or less to your original expression)

for {
  item <- list
  itemConverted = item.accept(this, arg).asInstanceOf[T]
  itemNonNull = itemConverted if itemConverted != 0
} yield itemNonNull

Using collect would be possible but it would likely call accept twice on most arguments because of the isDefinedAt test of the partial function:

list collect {
  case i if i.accept(this, arg).asInstanceOf[T] != null => i.accept(this, arg).asInstanceOf[T]
}

One would need to use some memoising (or smart extractors) to avoid this.

Debilski
  • 66,976
  • 12
  • 110
  • 133
8

If you are concerned about performance, you can add .view

list.view.map(_.accept(this, arg).asInstanceOf[T]).filter(_ != null)

view causes the traversal to become lazy, thus the map and filter will be performed in one pass over the list rather than two separate passes.

If you are concerned about reusing this pattern, you can define your own helper function:

def mapNN[A,B](list: List[A])(f: A => B) = {
  list.view.map(f(_)).filter(_ != null)
}

mapNN(list)(_.accept(this, arg).asInstanceOf[T])

Testing...

> mapNN(List(1,2,3))(x => if (x%2==0) x else null).toList
res7: List[Any] = List(2)
Dan Burton
  • 53,238
  • 27
  • 117
  • 198
  • Can you explain more on this part _thus the map and filter will be performed in one pass over the list rather than two separate passes_ ? How can it only need one pass to apply 2 transformations? – Minh Thai Aug 22 '17 at 02:27
  • 2
    @MinhThai Here's a decent explanation of `view`: https://stackoverflow.com/a/6799739/208257 Basically, it makes transformations lazy. It delays even doing a single pass, right up until you call `toList`, at which point it does a single pass, applying each transformation incrementally as it goes. – Dan Burton Aug 23 '17 at 23:58