Упрощение преобразований вложенных функторов - PullRequest
0 голосов
/ 29 сентября 2018

Скажем, у меня есть 2 функтора f0 и f1, и у меня есть некоторый код, который выглядит как -

f0.map(v0 => f1.map(v1 => f0f1(v0, v1)))

Есть ли способ упростить это так, что я могу использовать для выражения и сделатьочиститель кода -

for { 
  v0 <- f0
  v1 <- f1
} yield f0f1(v0, v1)

Функция map доступна через синтаксический сахар, который выглядит как -

  implicit class FunctorOps[F[_], A](fa: F[A]) {
    def F = implicitly[Functor[F]]
    def map[B](ab: A => B): F[B] = F.map(fa)(ab)
  }

Ответы [ 2 ]

0 голосов
/ 03 октября 2018

Я не думаю, что то, что вы ищете, возможно в принципе.

Хороший синтаксис for, который вы ищете, переводит что-то вроде

f0.flatMap(v0 => (f1.map(v1 => f0f1(v0, v1))))

Проблема здесь заключается вflatMap часть.Чтобы быть flatMap, вам нужен Monad, а не только Functor или хотя бы что-то с экземпляром FlatMap.Но Monad с, в отличие от Functor с, не сочиняют.Таким образом, невозможно получить вложенные Monad автоматически из F[_] и G[_], даже если оба они Monad s.[1]

Вы можете получить хороший синтаксис, используя некоторые монадные преобразователи, но они не существуют (и не могут существовать) для всех монад.[2]

Для монад, где есть трансформаторы, возможно что-то вроде того, что вы хотите:

val l: List[Int]      = List(1,2,3)
val o: Option[String] = Some("abc")

val ol: OptionT[List, String] = for {
  a <- OptionT.liftF(l)
  b <- OptionT.fromOption[List](o)
} yield a + b.toString

Есть scala docs ([3]), если вы заинтересованы в OptionT.

Неважно, приятнее это или нет, лежит в глазах смотрящего.

Как это ни печально, если вам это нужно чаще, вы можете написать свои собственные вспомогательные функции в духе

def combine2[F[_]: Functor, G[_]: Functor, A, B, C](
    fa: F[A],
    gb: G[B])(
    f: (A, B) => C
  ): F[G[C]] =
    fa.map(a => gb.map(b => (f(a, b))))

Если вы не хотите этого делать, одну вещь, которую вы можете сделать, - это то, что уже упоминал @ andrey-tyukin.Но я согласен, что, вероятно, лучше всего вкладывать вызовы в map.

Еще одна вещь, на которую вы, возможно, захотите взглянуть, это Nested [4].Это не поможет вам в данном конкретном случае, но может помочь вам уменьшить количество вложенных map s в будущем.

-

[1] http://blog.tmorris.net/posts/monads-do-not-compose/

[2] Почему в Хаскеле нет трансформатора ввода-вывода?

[3] https://typelevel.org/cats/api/cats/data/OptionT.html

[4] https://typelevel.org/cats/api/cats/data/Nested.html

0 голосов
/ 29 сентября 2018

Предполагая, что f0: F0[X] и f1: F1[Y], где и F0, и F1 имеют Functor экземпляров, а f: (X, Y) => Z, for -эквивалент, эквивалентный

f0.map(x => f1.map(y => f(x, y)))

будет

for (x <- f0) yield for (y <- f1) yield f(x, y)

Пример:

val f0 = Option(42)
val f1 = List(1, 2, 3)
for (x <- f0) yield for (y <- f1) yield x * y

производит:

res1: Option[List[Int]] = Some(List(42, 84, 126))

, тогда как

for (y <- f1) yield for (x <- f0) yield x * y

производит

res2: List[Option[Int]] = List(Some(42), Some(84), Some(126))

Я не уверен, что понимание for намного чище, чем вложенные map s в этом случае.

...