Несколько flatMaps в Scala

вместо xs map f map g эффективнее писать xs map { x => g(f(x)) }, а так же для нескольких filter операции.

если у меня есть два или более flatMaps подряд, есть ли способ объединить их в один, что, возможно, более эффективно? например,

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 flatMaps (или использование 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-тип ввода для следующей функции. По крайней мере, в общем случае произвольных типов и произвольного числа функций это звучит несколько бросающий вызовом.