微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

不能使用 flatMap 作为自写 monad 实例的扩展方法 问题 1. 您正在使用非参数类型的非不透明类型别名解决方案 1. 使用不透明类型别名问题 2这种类型不是 monad

如何解决不能使用 flatMap 作为自写 monad 实例的扩展方法 问题 1. 您正在使用非参数类型的非不透明类型别名解决方案 1. 使用不透明类型别名问题 2这种类型不是 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 不是 Main.Optional[Int] 的成员。
尝试了一个扩展方法,但无法完全构造:
cat.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,它有很多解,比如>

  1. F[X] = Int | X,A = Null
  2. F[X] = X | Null,A = Int
  3. F[X] = A | Null,A = Nothing
  4. F[X] = [X] =>> X,A = Int | Null

所以 Scala 自然不会尝试猜测。

尽管scala 3编译器在这里可以使用隐式\上下文值等附加信息,但是这里匹配Monad的优先级最高的隐式值是

    given Monad[Optional]

现在可以尝试申请 toFlatMapOps[F = Maybe](maybeInt1 : Int | Null) 然后有了 F[X] = X | Null,您需要计算 A,知道 F[A] = Null | A 并且它也有许多合理的解决方案

  1. A = Int
  2. 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)) 

第一种方法尝试使用给定的计算出的 monadic 值解决缺失值,第二种方法只是过滤掉那些。 那么,当评估这样的事情时,我们期望看到什么?

orElse(maybeInt1.map(filterOne))(3)

我们可能取非空,然后用缺失的位置替换 1,然后立即使用提供的 3 修复它。因此,我希望看到 3,但实际上,我们获得 null 作为结果,因为 null 在包装值内考虑为 flatMap 期间外部 Optional 的缺失分支。 这是因为这种天真的定义类型违反了 left-identity law

更新 关于comment by @n-pronouns-m 这个定义如何违反左身份法。 左身份声明

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 为第一个参数中的每一个返回 null,所以 pure(a).flatMap(f) == nullf(a) == 3

,

Odersky 的 response 主题

如果您将 Null 设为单独的类,或者您使用 -Yexplicit-nulls。

事物为 Null 的方式是底部类型。所以每个类的实例 Optional[C] 实际上是 C。

我尝试将 Optional 的定义更改为

type Optional[A] = A

然后也找不到隐式。所以 问题看起来根本不是联合类型的问题。如果 Optional[A] 定义了一个真正有效的联合。它看起来很像一个 HK 类型推断的局限性在于它不能推断身份。 我认为这实际上是预期的。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。