不能在自写的 monad 实例上使用 flatMap 作为扩展方法
Can't use flatMap as an extension method on a self-written instance of a monad
我试过在 WriterT 上使用 flatMap 并且成功了。
所以问题可能出在我的类型上,但我找不到它有什么问题。
import cats.Monad
import cats.syntax.flatMap._
object Main extends App {
type Optional[A] = A | Null
val maybeInt1: Optional[Int] = 1
val maybeInt2: Optional[Int] = null
given Monad[Optional] with {
def pure[A](x: A): Optional[A] = x
def flatMap[A, B](fa: Optional[A])(f: A => Optional[B]): Optional[B] = {
fa match {
case null => null
case a: A => f(a)
}
}
def tailRecM[A, B](a: A)(f: A => Optional[Either[A, B]]): Optional[B] = {
f(a) match {
case null => null
case Left(a1) => tailRecM(a1)(f)
case Right(b) => b
}
}
}
def f[F[_]: Monad, A, B](a: F[A], b: F[B]) = a.flatMap(_ => b)
println(Monad[Optional].flatMap(maybeInt1)(_ => maybeInt2)) //OK: null
println(f[Optional, Int, Int](maybeInt1, maybeInt2)) // OK: null
println(maybeInt1.flatMap(_ => maybeInt2)) // Compilation Error
}
错误是:
value flatMap is not a member of Main.Optional[Int].
An extension method was tried, but could not be fully constructed:
cats.syntax.flatMap.toFlatMapOps([A] =>> Any), A(given_Monad_Optional)
您的定义有几个问题。
问题 1。您正在对非参数类型使用非不透明类型别名
即type Optional[A] = A | Null
是类型表达式,会尽快展开。
当您使用它作为结果类型时,您实际得到的是
val maybeInt1: Int | Null = 1
val maybeInt2: Int | Null = null
所以当编译编译器有类似
的东西时
implicit def toFlatMapOps[F[_], A](fa: F[A])(implicit F: Monad[F]): MonadOps[F, A]
从 scala 2 库或 scala 3 中的等效扩展导入,
最后来到 maybeOption.flatMap
,
然后尝试应用以前的扩展方法,
它无法对表达式进行类型检查
toFlatMapOps(maybeInt1).flatMap(_ => maybeInt2)
所以现在你有Int | Null
作为参数,因为Optional
已经扩展了,需要计算相应的F[_]
和A
,它有很多解决方案,例如
F[X] = Int | X , A = Null
F[X] = X | Null, A = Int
F[X] = A | Null, A = Nothing
F[X] = [X] =>> X, A = Int | Null
所以 scala 自然无法猜测。
尽管scala 3编译器可以在这里使用implicit\contextual值等附加信息,但这里匹配Monad
的隐含值优先级最高的是
given Monad[Optional]
现在可以尝试申请了
toFlatMapOps[F = Maybe](maybeInt1 : Int | Null)
然后有了 F[X] = X | Null
你需要计算 A
知道 F[A] = Null | A
并且也有许多合理的解决方案
A = Int
A = Int | Null
所以即使 scala 在第一步不会失败,它也会卡在这里
解决方案 1. 使用不透明类型别名
将 scalacOptions += "-Yexplicit-nulls"
添加到您的 sbt 配置并尝试此代码
import cats.Monad
import cats.syntax.flatMap.given
object Optional:
opaque type Optional[+A] >: A | Null = A | Null
extension [A] (oa: Optional[A]) def value : A | Null = oa
given Monad[Optional] with
def pure[A](x: A): Optional[A] = x
def flatMap[A, B](fa: A | Null)(f: A => B | Null) =
if fa == null then null else f(fa)
def tailRecM[A, B](a: A)(f: A => Optional[Either[A, B]]): Optional[B] =
f(a) match
case null => null
case Left(a1) => tailRecM(a1)(f)
case Right(b) => b
type Optional[+A] = Optional.Optional[A]
@main def run =
val maybeInt1: Optional[Int] = 1
val maybeInt2: Optional[Int] = null
def f[F[_]: Monad, A, B](a: F[A], b: F[B]) = a.flatMap(_ => b)
println(Monad[Optional].flatMap(maybeInt1)(_ => maybeInt2)) //OK: null
println(f(maybeInt1, maybeInt2)) // OK: null
println(maybeInt1.flatMap(_ => maybeInt2)) // Compilation Error
问题 2. 这种类型不是 monad
即使在这个固定版本中 Optional[A]
也不符合基本的一元法则
考虑这段代码
def orElse[F[_], A](fa: F[Optional[A]])(default: => F[A])(using F: Monad[F]): F[A] =
fa.map(_.value).flatMap(fb => if fb == null then default else F.pure(fb : A))
def filterOne(x: Int): Optional[Int] = if x == 1 then null else x - 1
println(orElse(maybeInt1.map(filterOne))(3))
第一种方法尝试使用给定的计算单值来解决缺失值,第二种方法只是过滤掉那些。
那么,当评估这样的东西时,我们期望看到什么?
orElse(maybeInt1.map(filterOne))(3)
我们可能取非空,然后用缺失的地方替换 1
,然后立即使用提供的 3
修复它。所以我希望看到 3
但实际上,我们获得 null
作为结果,因为 null
在包装值内部被认为是 flatMap 期间外部 Optional
的缺失分支。
这是因为这种天真定义的类型违反了 left-identity law
更新
关于
这个定义如何违反左恒等式。
左身份表明
pure(a).flatMap(f) == f(a)
for all types A, B, and values a: A, f: A => Optional[B]
所以我们取 A = Optional[Int], B = Int, A = null, f(a) = if a == null then 3 else 2
pure(a) 仍然为 null,flatMap returns 每个第一个参数都为 null,所以
pure(a).flatMap(f) == null
而 f(a) == 3
Odersky 的 response 主题
It does work if you make Null a separate class, or you compile with
-Yexplicit-nulls.
The way things are Null is a bottom type. So every class instance of
Optional[C] is in fact C.
I tried to change the definition of Optional to
type Optional[A] = A
then the implicit is not found either. So the
problem looks like not a problem with union types at all. If
Optional[A] defines a real union it works. It looks rather like a
limitation of HK type inference that it cannot infer identities.
That's actually expected, I think.
我试过在 WriterT 上使用 flatMap 并且成功了。
所以问题可能出在我的类型上,但我找不到它有什么问题。
import cats.Monad
import cats.syntax.flatMap._
object Main extends App {
type Optional[A] = A | Null
val maybeInt1: Optional[Int] = 1
val maybeInt2: Optional[Int] = null
given Monad[Optional] with {
def pure[A](x: A): Optional[A] = x
def flatMap[A, B](fa: Optional[A])(f: A => Optional[B]): Optional[B] = {
fa match {
case null => null
case a: A => f(a)
}
}
def tailRecM[A, B](a: A)(f: A => Optional[Either[A, B]]): Optional[B] = {
f(a) match {
case null => null
case Left(a1) => tailRecM(a1)(f)
case Right(b) => b
}
}
}
def f[F[_]: Monad, A, B](a: F[A], b: F[B]) = a.flatMap(_ => b)
println(Monad[Optional].flatMap(maybeInt1)(_ => maybeInt2)) //OK: null
println(f[Optional, Int, Int](maybeInt1, maybeInt2)) // OK: null
println(maybeInt1.flatMap(_ => maybeInt2)) // Compilation Error
}
错误是:
value flatMap is not a member of Main.Optional[Int].
An extension method was tried, but could not be fully constructed:
cats.syntax.flatMap.toFlatMapOps([A] =>> Any), A(given_Monad_Optional)
您的定义有几个问题。
问题 1。您正在对非参数类型使用非不透明类型别名
即type Optional[A] = A | Null
是类型表达式,会尽快展开。
当您使用它作为结果类型时,您实际得到的是
val maybeInt1: Int | Null = 1
val maybeInt2: Int | Null = null
所以当编译编译器有类似
的东西时implicit def toFlatMapOps[F[_], A](fa: F[A])(implicit F: Monad[F]): MonadOps[F, A]
从 scala 2 库或 scala 3 中的等效扩展导入,
最后来到 maybeOption.flatMap
,
然后尝试应用以前的扩展方法,
它无法对表达式进行类型检查
toFlatMapOps(maybeInt1).flatMap(_ => maybeInt2)
所以现在你有Int | Null
作为参数,因为Optional
已经扩展了,需要计算相应的F[_]
和A
,它有很多解决方案,例如
F[X] = Int | X , A = Null
F[X] = X | Null, A = Int
F[X] = A | Null, A = Nothing
F[X] = [X] =>> X, A = Int | Null
所以 scala 自然无法猜测。
尽管scala 3编译器可以在这里使用implicit\contextual值等附加信息,但这里匹配Monad
的隐含值优先级最高的是
given Monad[Optional]
现在可以尝试申请了
toFlatMapOps[F = Maybe](maybeInt1 : Int | Null)
然后有了 F[X] = X | Null
你需要计算 A
知道 F[A] = Null | A
并且也有许多合理的解决方案
A = Int
A = Int | Null
所以即使 scala 在第一步不会失败,它也会卡在这里
解决方案 1. 使用不透明类型别名
将 scalacOptions += "-Yexplicit-nulls"
添加到您的 sbt 配置并尝试此代码
import cats.Monad
import cats.syntax.flatMap.given
object Optional:
opaque type Optional[+A] >: A | Null = A | Null
extension [A] (oa: Optional[A]) def value : A | Null = oa
given Monad[Optional] with
def pure[A](x: A): Optional[A] = x
def flatMap[A, B](fa: A | Null)(f: A => B | Null) =
if fa == null then null else f(fa)
def tailRecM[A, B](a: A)(f: A => Optional[Either[A, B]]): Optional[B] =
f(a) match
case null => null
case Left(a1) => tailRecM(a1)(f)
case Right(b) => b
type Optional[+A] = Optional.Optional[A]
@main def run =
val maybeInt1: Optional[Int] = 1
val maybeInt2: Optional[Int] = null
def f[F[_]: Monad, A, B](a: F[A], b: F[B]) = a.flatMap(_ => b)
println(Monad[Optional].flatMap(maybeInt1)(_ => maybeInt2)) //OK: null
println(f(maybeInt1, maybeInt2)) // OK: null
println(maybeInt1.flatMap(_ => maybeInt2)) // Compilation Error
问题 2. 这种类型不是 monad
即使在这个固定版本中 Optional[A]
也不符合基本的一元法则
考虑这段代码
def orElse[F[_], A](fa: F[Optional[A]])(default: => F[A])(using F: Monad[F]): F[A] =
fa.map(_.value).flatMap(fb => if fb == null then default else F.pure(fb : A))
def filterOne(x: Int): Optional[Int] = if x == 1 then null else x - 1
println(orElse(maybeInt1.map(filterOne))(3))
第一种方法尝试使用给定的计算单值来解决缺失值,第二种方法只是过滤掉那些。 那么,当评估这样的东西时,我们期望看到什么?
orElse(maybeInt1.map(filterOne))(3)
我们可能取非空,然后用缺失的地方替换 1
,然后立即使用提供的 3
修复它。所以我希望看到 3
但实际上,我们获得 null
作为结果,因为 null
在包装值内部被认为是 flatMap 期间外部 Optional
的缺失分支。
这是因为这种天真定义的类型违反了 left-identity law
更新
关于
pure(a).flatMap(f) == f(a)
for all types A, B, and values a: A, f: A => Optional[B]
所以我们取 A = Optional[Int], B = Int, A = null, f(a) = if a == null then 3 else 2
pure(a) 仍然为 null,flatMap returns 每个第一个参数都为 null,所以
pure(a).flatMap(f) == null
而 f(a) == 3
Odersky 的 response 主题
It does work if you make Null a separate class, or you compile with -Yexplicit-nulls.
The way things are Null is a bottom type. So every class instance of Optional[C] is in fact C.
I tried to change the definition of Optional to
type Optional[A] = A
then the implicit is not found either. So the problem looks like not a problem with union types at all. If Optional[A] defines a real union it works. It looks rather like a limitation of HK type inference that it cannot infer identities. That's actually expected, I think.