scala - 在存在更高种类类型的情况下,如何控制模式匹配中绑定(bind)变量的推断类型

标签 scala pattern-matching abstract-data-type algebraic-data-types higher-kinded-types

(这是基于 http://bertails.org/2015/02/15/abstract-algebraic-data-type 上的文章)

首先,我定义 scala.Option 的抽象版本。

import scala.language.higherKinds

trait OptionSig {
  type Option[+_]
  type Some[+A] <: Option[A]
  type None <: Option[Nothing]
}

abstract class OptionOps[Sig <: OptionSig] extends Extractors[Sig] {
  def some[A](x: A): Sig#Some[A]
  def none: Sig#None
  def fold[A, B](opt: Sig#Option[A])(ifNone: => B, ifSome: A => B): B
}

我希望能够在 Sig#Option[A] 上使用模式匹配,因此 Extractors 看起来像这样:

trait Extractors[Sig <: OptionSig] { self: OptionOps[Sig] =>

  object Some {
    def unapply[A](opt: Sig#Option[A]): scala.Option[A] =
      fold(opt)(scala.None, a => scala.Some(a))
  }

  object None {
    def unapply[A](opt: Sig#Option[A]): Option[Unit] =
      fold(opt)(scala.Some(()), _ => scala.None)
  }

}

现在我可以编写这个程序了:

class Program[Sig <: OptionSig](implicit ops: OptionOps[Sig]) extends App {

  import ops._

  val opt: Sig#Option[Int] = some(42)

  opt match {
    case None(_)  => sys.error("")
    case Some(42) => println("yay")
    case Some(_)  => sys.error("")
  }

}

我可以用这个实现来测试它。

trait ScalaOption extends OptionSig {

  type Option[+A] = scala.Option[A]
  type Some[+A]   = scala.Some[A]
  type None       = scala.None.type

}

object ScalaOption {

  implicit object ops extends OptionOps[ScalaOption] {

    def some[A](x: A): ScalaOption#Some[A] = scala.Some(x)

    val none: ScalaOption#None = scala.None

    def fold[A, B](opt: ScalaOption#Option[A])(ifNone: => B, ifSome: A => B): B =
      opt match {
        case scala.None    => ifNone
        case scala.Some(x) => ifSome(x)
      }

  }

}

object Main extends Program[ScalaOption]

看起来可行,但有一件烦人的事情我无法弄清楚。

与,scala.OptionOption(42)中s的类型匹配{ case s @ Some(42) => s }Some[Int]。但在上面的代码片段中,它是 Sig#Option[Int],我想将其改为 Sig#Some[Int]

所以我尝试了以下方法来更接近 scalac 为其案例类生成的内容:

trait Extractors[Sig <: OptionSig] { self: OptionOps[Sig] =>

  object Some {
    def unapply[A](s: Sig#Some[A]): scala.Option[A] =
      fold(s)(scala.None, a => scala.Some(a))
  }

  object None {
    def unapply(n: Sig#None): Option[Unit] =
      fold(n)(scala.Some(()), (_: Any) => scala.None)
  }

}

但现在我收到如下警告:

[warn] Main.scala:78: abstract type pattern Sig#None is unchecked since it is eliminated by erasure
[warn]     case None(_)  => sys.error("")

我不确定为什么会发生这种情况,因为 Sig#NoneSig#Option[Int] 的子类型,并且这是在编译时已知的.

运行时仍然可以,但推断的类型仍然不是我期望的类型。

所以问题是

  • 尽管有子类型信息,为什么这里还是提到类型删除?
  • 如何在 (some(42): Sig#Option[Int]) match { case s @ 中获取 sSig#Option[Int]一些(42) => s }

最佳答案

不幸的是,你无法做你想做的事。问题是 scalac 知道 Sig#None <: Sig#Option[A] 还不够。 ,它必须能够验证传递给 unapply 的值是否正确事实上,Sig#None 。这适用于 scala.Option ,因为编译器可以生成 instanceof检查以验证类型实际上是 SomeNone在将其传递给 unapply 之前方法。如果检查失败,它将跳过该模式(并且 unapply 永远不会被调用)。

就你而言,因为 scalac 只知道 optSig#Option[Int] ,它无法做任何事情来保证该值实际上是 SomeNone然后将其传递给 unapply .

那么它有什么作用呢? 它无论如何都会传递值!这是什么意思?好吧,让我们稍微修改一下你的提取器:

trait Extractors[Sig <: OptionSig] { self: OptionOps[Sig] =>
  object Some {
    def unapply[A](s: Sig#Some[A]): scala.Option[A] =
      fold(s)(scala.None, a => scala.Some(a))
  }

  object None {
    def unapply(n: Sig#None): Option[Unit] =
      scala.Some(())
  }
}

我们所做的就是停止使用 foldNone案件。因为我们知道参数必须是 Sig#None ,为什么还要打电话fold , 正确的?我的意思是,我们不会期望 Sig#Some传递到这里,对吗?

当我们运行此示例时,您将看到 RuntimeException ,因为我们的第一个模式匹配成功并调用 ??? 。以scala.Option为例,该模式将会失败,因为它受到生成的 instanceof 的保护。检查。

我举了另一个例子,它显示了另一种危险,其中我们向Sig#Some添加了一个小约束。 ,这让我们避免了 foldSome案例:https://gist.github.com/tixxit/ab99b741d3f5d2668b91

无论如何,您的具体情况在技术上是安全的。我们知道您使用过fold因此与 Sig#Option 一起使用是安全的。问题是scalac不知道。

关于scala - 在存在更高种类类型的情况下,如何控制模式匹配中绑定(bind)变量的推断类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29067328/

相关文章:

scala - Scala:类型类和ADT之间的区别?

scala - ForkJoinPool 在只有两个 worker 的情况下被阻塞

scala - 使用案例类显示表达式

scala - 如何使用 Scala IDE 和 Maven 构建 spark 应用程序?

json - 如何将自定义编码器添加到 akka http?

haskell - 模式匹配变量的范围

c - .h注释: previous declaration of 'QueueADT' was here typedef struct { } *QueueADT;

java - 如何使用正则表达式获取第一个匹配字符串

scala - Scala 中的模式匹配 Jackson JSON

java - JAVA 中的递归前序遍历运行直至堆栈溢出(BST)