Избегайте шаблонов с помощью OptionT (естественное преобразование?)

У меня есть следующие методы:

trait Tr[F[_]]{

    def getSet(): F[Set[String]]

    def checksum(): F[Long]

    def value(): F[String]

    def doRun(v: String, c: Long, s: Set[String]): F[Unit]
}

теперь я хочу написать следующее для понимания:

import cats._
import cats.data.OptionT
import cats.implicits._

def fcmprhn[F[_]: Monad](F: Tr[F]): OptionT[F, Unit] =
  for {
    set <- OptionT {
      F.getSet() map { s =>
        if(s.nonEmpty) Some(s) else None
      }
    }
    checksum <- OptionT.liftF(F.checksum())
    v <- OptionT.liftF(F.value())
    _ <- OptionT.liftF(F.doRun(v, checksum, set))
    //can be lots of OptionT.liftF here
} yield ()

Как видите, шаблонного OptionT слишком много. Есть ли способ избежать этого?

Я думаю, что могу использовать F ~> OptionT[F, ?]. Можете ли вы что-нибудь предложить?


person St.Antario    schedule 18.07.2018    source источник
comment
@AndreyTyukin 2) Нет, но на самом деле им нужно было позже выполнить эффективные вычисления. Добавлен   -  person St.Antario    schedule 19.07.2018


Ответы (2)


Один из подходов может состоять в том, чтобы вложить часть for-comprehension "только F" в один liftF:

def fcmprhn[F[_]: Monad](F: Tr[F]): OptionT[F, Unit] =
  for {
    set <- OptionT {
      F.getSet() map { s =>
        if(s.nonEmpty) Some(s) else None
      }
    }
    _ <- OptionT.liftF {
      for {
        checksum <- F.checksum()
        v <- F.value()
        _ <- F.doRun(v, checksum, set)
        // rest of F monad for-comprehension
      } yield ()
    }
  } yield ()
person Joe K    schedule 18.07.2018
comment
Это имеет смысл, но... вложено для понимания... Все равно спасибо - person St.Antario; 19.07.2018
comment
Кроме того, я думаю, что первый раздел понимания можно было бы написать проще: set <- OptionT.liftF(F.getSet()) if (set.nonEmpty), поскольку OptionT имеет метод withFilter. - person Joe K; 19.07.2018

Вместо этого вы можете написать его в «мтл-стиле». mtl-style относится к библиотеке mtl в Haskell, но на самом деле это просто означает, что вместо того, чтобы кодировать эффекты как значения (т. Это означает, что вместо использования OptionT[F, Unit] в качестве типа возвращаемого значения мы можем просто использовать F[Unit] в качестве типа возвращаемого значения, потому что F должен уметь обрабатывать ошибки.

Это немного упрощает написание кода, подобного вашему, но его эффект усиливается, когда вы добавляете монадные преобразователи в стек. Прямо сейчас вам нужно поднять только один раз, но что, если вы захотите StateT[OptionT[F, ?], S, Unit] в будущем. В стиле mtl все, что вам нужно сделать, это добавить еще одно ограничение класса типа.

Вот как будет выглядеть ваш код, написанный в стиле mtl:

def fcmprhn[F[_]](F: Tr[F])(implicit E: MonadError[F, Unit]): F[Unit] =
  for {
    set <- OptionT {
      F.getSet() flatMap { s =>
        if(s.nonEmpty) E.pure(s) else E.raiseError(())
      }
    }
    checksum <- F.checksum()
    v <- F.value()
    _ <- F.doRun(v, checksum, set)
} yield ()

И теперь, когда вы запускаете программу, вы можете указать, что F[_] будет чем-то вроде того, что у вас было до OptionT[F, ?]:

fcmprhn[OptionT[F, ?]](OptionT.liftF(yourOriginalTr))
person Luka Jacobowitz    schedule 23.07.2018
comment
Я не совсем понимаю, почему вы заменили Some/None на pure/raiseError`. Можете немного расширить? - person St.Antario; 23.07.2018
comment
Я добавил еще несколько пояснений :) - person Luka Jacobowitz; 23.07.2018
comment
У меня все еще есть некоторое непонимание этого подхода. Вы указали неявный параметр E: MonadError[F, Unit], но даже в случае cats.effect.IO у нас есть только MonadError[IO, Throwble]. Но экземпляры OptionT полагаются на экземпляры F... Не могли бы вы прояснить этот момент? - person St.Antario; 28.07.2018
comment
@St.Antario OptionT[F, A] имеет экземпляр MonadError для Unit, поэтому вы можете использовать его :) - person Luka Jacobowitz; 28.07.2018