[英]Problem with given instances writing MTL style code with Scala cats
我正在尝试编写一些 Scala 代码以具有mtl 样式的自定义行为。 例如,为了公开对特定效果进行抽象的“写入数据库”功能,我编写了自己的类型类:
trait CanPersist[M[_]]:
def persistToDB[A](a: A): M[Unit]
given CanPersist[IO] with
def persistToDB[A](a: A): IO[Unit] = IO(???) // Write to DB
IO 实例可以轻松实现,但我感兴趣的是自动为任何基于 IO 的 monad 堆栈提供实例:
// If a Transformer wraps a Monad that can persist then it can persist too
given persistTA[M[_]: CanPersist: Monad, T[_[_], _]: MonadTransformer]:
CanPersist[[A] =>> T[M, A]] with
def persistToDB[A](a: A): T[M, Unit] =
summon[MonadTransformer[T]].lift(summon[CanPersist[M]].persistToDB(a))
问题显然是猫没有定义自己的MonadTransformer
类型类; 幸运的是,编写自己的代码非常简单:
trait MonadTransformer[T[_[_], _]]:
def lift[M[_]: Monad, A](ma: M[A]): T[M, A]
// A Monad Transformer is a Monad if it wraps a Monad
given monadTA[M[_]: Monad, T[_[_], _]: MonadTransformer]: Monad[[A] =>> T[M, A]] with
def pure[A](a: A): T[M, A] = ??? // implementations are not relevant
def flatMap[A, B](fa: T[M, A])(f: A => T[M, B]): T[M, B] = ???
def tailRecM[A, B](a: A)(f: A => T[M, Either[A, B]]): T[M, B] = ???
// Both WriterT and EitherT are Monad Transformers
given writerMT[L: Monoid]: MonadTransformer[[M[_], A] =>> WriterT[M, L, A]] with
def lift[M[_]: Monad, A](ma: M[A]): WriterT[M, L, A] =
WriterT.liftF(ma)
given eitherMT[Err]: MonadTransformer[[M[_], A] =>> EitherT[M, Err, A]] with
def lift[M[_]: Monad, A](ma: M[A]): EitherT[M, Err, A] =
EitherT.liftF(ma)
现在进入实际使用CanPersist
功能的代码:
def saveIntString[M[_]: Monad]
(int: Int, string: String)
(using P:CanPersist[M])
: M[String] =
for {
_ <- P.persistToDB(int)
_ <- P.persistToDB(string)
} yield "done"
val res: WriterT[IO, String, String] = saveIntString(2, "test")
// Does not compile:
// no implicit argument of type CanPersist[M] was found for parameter P of method saveIntString
// where: M is a type variable with constraint <: [V] =>> cats.data.WriterT[cats.effect.IO, String, V]
// I found:
// persistTA[M, T]
// But given instance persistTA does not match type CanPersist[M].
问题是编译器显然无法导出正确的实例; 这让我很困惑。 我认为编译器将能够导出正确的实例:
WriterT
有一个Transformer
实例IO
有一个CanPersist
实例WriterT
是一个Transformer
和IO
一个可以持久WriterT[IO, _, _]
的 monad 也应该有一个CanPersist
实例有没有办法以这种方式定义描述的Transformer
类型类? 编译器可以派生这样的实例还是在 Scala 中是不可能的?推理问题似乎是您链接的特定 MTL 实现依赖于MonadPartialOrder等特征而不是MonadTransformer
-typeclasses 的原因之一。
基本上,这里发生的是:当你想从F
到G
MonadPartialOrder
- 方法要求从F
到G
的桥梁G
解构为[X] =>> T[M, X]
,然后找到一个花哨的通用桥梁建造者T
,然后使用该装置建造从F
到([X] =>> T[M, X])
的桥梁([X] =>> T[M, X])
。 因此, cats.mtl
的方法要简单得多,并且对推理算法的要求要低得多。 这就是为什么cats.mtl
有效,而您的方法无效。
我将首先概述如何修复您的示例,然后我将推测一下您的方法为何不起作用。
MonadPartialOrder
的解决方案以下是我尝试使用MonadPartialOrder
中的cats.mtl
解决您的问题的方法:
import cats.data.WriterT
import cats.syntax.all._
import cats.mtl.MonadPartialOrder
trait CanPersist[M[_]]:
def persistToDB[A](a: A): M[Unit]
given CanPersist[IO] with
def persistToDB[A](a: A): IO[Unit] = IO(???) // Write to DB
given persistTA[F[_]: CanPersist: Monad, G[_]]
(using mpo: MonadPartialOrder[F, G]): CanPersist[G] with
def persistToDB[A](a: A): G[Unit] =
mpo(summon[CanPersist[F]].persistToDB(a))
def saveIntString[M[_]: Monad]
(int: Int, string: String)
(using P:CanPersist[M])
: M[String] =
for {
_ <- P.persistToDB(int)
_ <- P.persistToDB(string)
} yield "done"
def res: WriterT[IO, String, String] = saveIntString(2, "test")
@main def main(): Unit =
println("Run it with 'sbt clean compile run'")
基本思想是使用MonadPartialOrder[F, G]
从F
到G
,而不是要求MonadTransformer[T]
从F
到[X] =>> T[F, X]
。
这在 Scala 3.1.2 上编译和运行得很好,如果你想尝试一下,这里有一个完整的build.sbt
:
import Dependencies._
ThisBuild / scalaVersion := "3.1.2"
ThisBuild / version := "0.1.0-SNAPSHOT"
ThisBuild / organization := "com.foobarbaz"
ThisBuild / organizationName := "example"
lazy val root = (project in file("."))
.settings(
name := "cats-mtl-so-question-72407103",
scalacOptions += "-explaintypes",
libraryDependencies += scalaTest % Test,
libraryDependencies += "org.typelevel" %% "cats-core" % "2.7.0",
libraryDependencies += "org.typelevel" %% "cats-mtl" % "1.2.1",
libraryDependencies += "org.typelevel" %% "cats-effect" % "3.4-389-3862cf0",
)
您解释中的逻辑对我来说似乎很好,所以我想说编译器目前无法推断出所需的类型类。 您的解决方案不起作用(而cats.mtl
)的原因是您的解决方案试图在比cats.mtl
更高的抽象级别上工作。
一个普通的 MTL 实现通常试图解决的问题看起来有点像这样:
对于一个固定属性
P
和两个固定单子LameMonad
和FancyMonad
,找到一种方法将P
从LameMonad
提升到FancyMonad
。
这是为一些有用的属性P
完成的(例如,您可以Ask
、 Tell
、访问和改变Stateful
的东西等等),以及LameMonad
和FancyMonad
的合理数量的不同组合,花式 monad 通常来自 lame通过应用一些 monad 转换器(例如来自于cats.data._
的那些)来创建 monads。 注意量词“for a few” 、 “for a proper amount”是如何出现在我们试图自动解决的问题陈述之外的元讨论中的。
现在,将此与您的代码进行对比,您在代码中使用以下签名向编译器致意:
given monadTA[M[_]: Monad, T[_[_], _]: MonadTransformer] // ... etc
上下文绑定: MonadTransformer
要求编译器解决一个看起来大致类似的问题
对于固定的
T
,找到一个唯一的构造证明,证明对于所有单子M
,[X] => T[M, X]
也是单子。
请注意for all
量词现在如何滑入我们试图自动化的任务的问题陈述中,并且还要注意现在编译器应该以某种方式推断“正确”的方式来匹配更高种类的Foo
与[A] =>> T[M, A]
具有更高种类的M
。
匹配[A] =>> T[M, A]
的任务很棘手(由于子类化/继承甚至比在 Haskell 中更棘手),而且实际上有些不明确。 例如WriterT[IO, String, V]
可以有多种分解方式:是
[X[_], Y] =>> WriterT[X, String, Y]
应用于IO
和V
或者是
[X[_], Y] =>> WriterT[IO, Y, X[V]]
应用于Id[_]
和String
还是任何其他组合? 一些约定(首先采用最右边的参数等)似乎在最常见的情况下有效,但显然不适用于您的特定情况。
因此,在无法确定的情况下,我假设所有那些对更高种类的通用量化以某种方式设法使编译器严重混淆,以至于该方法变得不切实际。 我还假设这是cats.mtl
使用MonadPartialOrder
而不是MonadTransformer
-typeclasses 的原因之一: MonadPartialOrder[F, G]
告诉你你可以用G
做任何你可以用F
做的事情,对于两个固定的单子F
和G
。 这两个参数的种类都是* -> *
,这比所有那些更高种类的[X[_], Y] =>> Z[X, Y]
-lambdas 更加良性。
因此,重申一下,MTL 正在这样做:
For a few selected `P`, `F`, `G`, solve problem: "lift P from F to G"
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^
meta-level, interpreted by humans easy for compiler
而您正在尝试更接近此的东西(挥手挥手):
For a fixed `P`, solve: "for all `F`, `G`, lift `P` from `F` to `G`"
^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
meta-level, easy too hard for the compiler
这是足够的,但不是必需的(因此对编译器来说是不必要的困难)。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.