Несколько flatMaps в Scala
вместо xs map f map g
эффективнее писать xs map { x => g(f(x)) }
, а так же для нескольких filter
операции.
если у меня есть два или более flatMap
s подряд, есть ли способ объединить их в один, что, возможно, более эффективно? например,
def f(x: String) = Set(x, x.reverse)
def g(x: String) = Set(x, x.toUpperCase)
Set("hi", "bye") flatMap f flatMap g
// Set(bye, eyb, IH, BYE, EYB, ih, hi, HI)
3 ответов
чтобы сделать такое преобразование, нам нужно использовать некоторую идентичность, которую имеют операции. Например, как вы писали, map
и удостоверение
map f ∘ map g ≡ map (f ∘ g)
(где ∘
означает функциональную композицию-см. Function1.сочинять.(..); и ≡
означает эквивалентность выражений). Это потому, что классы с map
можно рассматривать как функторы, поэтому любая разумная реализация map
должны сохранить эту свойство.
С другой стороны, классы flatMap
и есть способ, как создать какой-то одноэлементный экземпляр (например, создание одноэлементного Set
) обычно образуют монады. Поэтому мы можем попытаться вывести некоторые преобразования из правил монады. Но единственное тождество, которое мы можем вывести для повторных приложений flatMap
is
(set flatMap f) flatMap g ≡ x flatMap { y => f(y) flatMap g }
, который является своего рода ассоциативность отношения на flatMap
состав. Это не поможет много для оптимизации вычислений (на самом деле это может сделать его хуже). Таким образом, вывод заключается в том, что нет аналогичной общей "оптимизации" идентичности для flatMap
.
нижняя строка: каждая функция, заданная Set.flatMap
создает новый Set
для каждого элемента, к которому он применяется. Мы не можем избежать создания таких промежуточных наборов, если мы полностью не забудем использовать composing flatMap
и решить проблему иным способом. Обычно этого не стоит, так как composing flatMap
s (или использование for(...) yield ..
) намного чище и читабельнее, и небольшой компромисс скорости обычно не является большой проблемой.
на scalaz существует способ создания таких функций, как a -> m b
и b -> m c
на a -> m c
(как и функция здесь, из String
до Set[String]
). Они называются функции Kleisli, кстати. В haskell это делается просто с >=>
об этих функциях. В scala вам придется быть немного более подробным (кстати, я немного изменил пример: я не мог заставить его работать с Set
, поэтому я использовал List
):
scala> import scalaz._, std.list._
import scalaz._
import std.list._
scala> def f(x: String) = List(x, x.reverse)
f: (x: String)List[String]
scala> def g(x: String) = List(x, x.toUpperCase)
g: (x: String)List[java.lang.String]
scala> val composition = Kleisli(f) >=> Kleisli(g)
composition: scalaz.Kleisli[List,String,java.lang.String] = scalaz.KleisliFunctions$$anon@37911406
scala> List("hi", "bye") flatMap composition
res17: List[java.lang.String] = List(hi, HI, ih, IH, bye, BYE, eyb, EYB)
подход, который вы описываете для фильтров, в основном пропускает создание промежуточных коллекций.
С flatMap
по крайней мере, внутренние коллекции создаются внутри функций, поэтому я не могу представить себе способ пропустить это создание без изменения функции.
Вы можете попробовать использовать представление, хотя я не уверен, что это делает что-нибудь полезное с flatMap.
в качестве альтернативы вы можете построить multiFlatMap
который создает окончательную коллекцию непосредственно из результатов функции, без заполнения промежуточных коллекций, возвращаемых из функций в новой коллекции.
не знаю, работает ли это. Я, по крайней мере, вижу некоторые серьезные проблемы с типом, поскольку вам нужно будет передать Seq функций, где каждая функция возвращает коллекцию A, где A-тип ввода для следующей функции. По крайней мере, в общем случае произвольных типов и произвольного числа функций это звучит несколько бросающий вызовом.