使用 OptionT 避免样板文件(自然变换?)
Avoiding boilerplate with OptionT (natural transform?)
我有以下方法:
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, ?]
。你能推荐点什么吗?
一种方法是将 for-comprehension 的 "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 ()
您可以将其写在 "mtl-style" 中。 mtl-style 指的是 haskell 中的 mtl 库,但实际上它只是意味着不是将效果编码为值(即 OptionT[F, ?]
),而是将它们编码为具有抽象效果的函数 F[_]
和使用 classes 类型赋予 F
能力。这意味着我们可以使用 F[Unit]
作为我们的 return 类型,而不是使用 OptionT[F, Unit]
作为我们的 return 类型,因为 F
必须能够处理错误。
这使得编写像您这样的代码更容易一些,但是当您将 monad 转换器添加到堆栈时,它的效果会被放大。现在您只需举起一次,但如果您以后想要 StateT[OptionT[F, ?], S, Unit]
怎么办?使用 mtl-style,您需要做的就是添加另一种类型 class 约束。
这是您的代码以 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))
我有以下方法:
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, ?]
。你能推荐点什么吗?
一种方法是将 for-comprehension 的 "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 ()
您可以将其写在 "mtl-style" 中。 mtl-style 指的是 haskell 中的 mtl 库,但实际上它只是意味着不是将效果编码为值(即 OptionT[F, ?]
),而是将它们编码为具有抽象效果的函数 F[_]
和使用 classes 类型赋予 F
能力。这意味着我们可以使用 F[Unit]
作为我们的 return 类型,而不是使用 OptionT[F, Unit]
作为我们的 return 类型,因为 F
必须能够处理错误。
这使得编写像您这样的代码更容易一些,但是当您将 monad 转换器添加到堆栈时,它的效果会被放大。现在您只需举起一次,但如果您以后想要 StateT[OptionT[F, ?], S, Unit]
怎么办?使用 mtl-style,您需要做的就是添加另一种类型 class 约束。
这是您的代码以 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))