it-roy-ru.com

withFilter вместо фильтра

Всегда ли эффективнее использовать withFilter вместо фильтра при последующем применении таких функций, как map, flatmap и т.д.?

Почему поддерживаются только карта, плоская карта и foreach? (Ожидаемые функции, такие как forall/также существуют) 

68
Kigyo

Из Scala docs :

Примечание: разница между c filter p и c withFilter p в том, что первая создает новую коллекцию, тогда как последний ограничивает только домен последующие операции map, flatMap, foreach и withFilter.

Таким образом, filter примет исходную коллекцию и создаст новую коллекцию, но withFilter будет не строго (то есть лениво) передавать нефильтрованные значения в последующие вызовы map/flatMap/withFilter, сохраняя второй проход через (отфильтрованную) коллекцию. Следовательно, это будет более эффективно при переходе к этим последующим вызовам методов.

На самом деле, withFilter специально разработан для работы с цепочками этих методов, и это то, для чего десагринг используется. Никаких других методов (таких как forall/exists) для этого не требуется, поэтому они не были добавлены к возвращаемому типу FilterMonadicwithFilter

105
Shadowlands

В дополнение к отличный ответ Shadowlands , я хотел бы привести интуитивный пример различия между filter и withFilter.

Давайте рассмотрим следующий код

val list = List(1, 2, 3)
var go = true
val result = for(i <- list; if(go)) yield {
   go = false
   i
}

Большинство людей ожидают, что result будет равен List(1). Это относится к Scala 2.8, потому что для понимания

val result = list withFilter {
  case i => go
} map {
  case i => {
    go = false
    i
  }
}

Как видите, перевод преобразует условие в вызов withFilter. До Scala 2.8, для понимания были переведены что-то вроде следующего:

val r2 = list filter {
  case i => go
} map {
  case i => {
    go = false
    i
  }
}

При использовании filter значение result будет совершенно другим: List(1, 2, 3). Тот факт, что мы делаем флаг gofalse, не влияет на фильтр, потому что фильтр уже сделан. Опять же, в Scala 2.8 эта проблема решается с помощью withFilter. Когда используется withFilter, условие оценивается каждый раз, когда к элементу обращаются внутри метода map.

Ссылка: - с.120, Scala в действии (охватывает Scala 2.10), Manning Publications, Milanjan Raychaudhuri - Мысли Одерского о понятном переводе

5
ZenLulz

Основная причина того, что forall/exist не реализованы, заключается в том, что сценарий использования:

  • вы можете лениво применять withFilter к бесконечному потоку/итерируемый
  • вы можете лениво применить другой с фильтром (и снова и снова) 

Для реализации forall/exist нам нужно получить все элементы, потеряв ленивость.

Так, например:

import scala.collection.AbstractIterator

class RandomIntIterator extends AbstractIterator[Int] {
  val Rand = new Java.util.Random
  def next: Int = Rand.nextInt()
  def hasNext: Boolean = true
}

//Rand_integers  is an infinite random integers iterator
val Rand_integers = new RandomIntIterator

val Rand_naturals = 
    Rand_integers.withFilter(_ > 0)

val Rand_even_naturals = 
    Rand_naturals.withFilter(_ % 2 == 0)

println(Rand_even_naturals.map(identity).take(10).toList)

//calling a second time we get
//another ten-Tuple of random even naturals
println(Rand_even_naturals.map(identity).take(10).toList)

Обратите внимание, что ten_Rand_even_naturals по-прежнему является итератором. Только / когда мы вызываем toList, случайные числа будут генерироваться и фильтроваться в цепочке

Обратите внимание, что map (identity) эквивалентно map (i => i) и используется здесь для преобразования объекта withFilter обратно в исходный тип (например, коллекция, поток, итератор)

0
frhack