【问题标题】:Avoiding boilerplate with OptionT (natural transform?)避免使用 OptionT 的样板(自然变换?)
【发布时间】:2018-12-26 22:40:57
【问题描述】:

我有以下方法:

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 ~&gt; OptionT[F, ?]。你能提出一些建议吗?

【问题讨论】:

  • @AndreyTyukin 2)他们没有,但他们实际上需要稍后执行有效的计算。已添加

标签: scala functional-programming monads scalaz scala-cats


【解决方案1】:

一种方法是将理解的“F only”部分嵌套在单个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 ()

【讨论】:

  • 说得通,但是……嵌套理解……还是谢谢
  • 另外,我认为第一个理解部分可以更简单地写成:set &lt;- OptionT.liftF(F.getSet()) if (set.nonEmpty),因为OptionT 有一个withFilter 方法。
【解决方案2】:

你可以用“mtl-style”来代替。 mtl-style 指的是 Haskell 中的 mtl 库,但实际上它只是意味着我们不是将效果编码为值(即OptionT[F, ?]),而是将它们编码为具有抽象效果F[_] 并使用类型赋予F 功能的函数类。这意味着我们可以使用F[Unit] 作为我们的返回类型,而不是使用OptionT[F, Unit] 作为我们的返回类型,因为F 必须能够处理错误。

这使得编写像您这样的代码更容易一些,但是当您将 monad 转换器添加到堆栈中时,它的效果会被放大。现在你只需举起一次,但如果你以后想要StateT[OptionT[F, ?], S, Unit] 怎么办。使用 mtl-style,您需要做的就是添加另一个类型类约束。

这是您的代码以 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[_] 指定为与​​您之前的@​​987654330@ 类似:

fcmprhn[OptionT[F, ?]](OptionT.liftF(yourOriginalTr))

【讨论】:

  • 我不太明白你为什么把Some/None换成pure/raiseError`。可以扩展一下吗?
  • 我添加了更多解释:)
  • 我对这种方法还是有一些误解。您指定了隐式参数E: MonadError[F, Unit],但即使是cats.effect.IO,我们也只有MonadError[IO, Throwble]。但是OptionT 实例依赖于F 的实例......你能澄清一下吗?
  • @St.Antario OptionT[F, A] 有一个MonadError 实例用于Unit,所以你可以使用那个:)
猜你喜欢
  • 2019-02-25
  • 2021-08-21
  • 1970-01-01
  • 2015-01-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-22
  • 2021-02-28
相关资源
最近更新 更多