【问题标题】:Declaring an empty pattern matching function声明一个空的模式匹配函数
【发布时间】:2021-01-30 07:19:29
【问题描述】:

如何声明一个空/无大小写模式匹配函数以满足类型定义?我很高兴该函数在被调用时会抛出运行时异常。

我正在处理Scala tutorial for Java programmers,其中我有一个工作函数,它在给定String => Int 映射的情况下对呈现为树的数学表达式执行变量替换。即使不应该存在变量(在这种情况下,在对表达式求导之后),我也想调用相同的代码路径,但是我找不到满足类型要求的简洁方法。这是我拥有的完整代码,但感觉不对:

abstract class Tree

case class Sum(l: Tree, r: Tree) extends Tree
case class Var(n: String) extends Tree
case class Const(v: Int) extends Tree

object CalculatorPatternsPrime {
  def eval(tree: Tree, env: String => Int): Int = tree match {
    case Sum(l, r) => eval(l, env) + eval(r, env)
    case Var(n) => env(n)
    case Const(v) => v
  }

  def eval(tree: Tree): Int = eval(tree, { case "ignore" => -1 })

  def derive(tree: Tree, v: String): Tree = tree match {
    case Sum(l, r) => Sum(derive(l, v), derive(r, v))
    case Var(n) if (n == v) => Const(1)
    case _ => Const(0)
  }

  def main(args: Array[String]): Unit = {
    val env: String => Int = { case "x" => 5 case "y" => 7 }
    val tree = Sum(
      Sum(Const(7), Var("y")),
      Sum(Var("x"), Var("x"))
    )
    println(eval(tree, env))
    println(derive(tree, "x"))
    println(eval(derive(tree, "x")))
  }
}

如您所见,我有一个虚拟的{ case "ignore" => -1 } 来使类型系统满意,并且代码工作正常,但我觉得必须有更好的方法来做到这一点。以下是我考虑过的两种选择:

  1. 只需将完整的方法主体写入eval(tree: Tree),而不是尝试调用eval(tree: Tree, env: String => Int),但这会重复处理SumConst 情况的代码。
  2. 使 env 成为可选/联合类型并让它抛出 NPE。

这里的惯用方法是什么?

【问题讨论】:

  • 我会删除第二个eval() 并给env 提供_ => -1 的默认值。

标签: scala


【解决方案1】:

有几种方法可以解决这个问题:

首先,正如 jwvh 所指出的,您可以通过对双参数 eval 中的第二个参数使用默认参数来消除单参数 eval

那么问题来了,那个默认参数应该是什么?

env 是一个String => Int,它是Function1[-A, +R] 的简写:在参数类型中是逆变的,在结果类型中是协变的。对于我们的目的,这意味着任何接受超类型String(包括String)并导致子类型Int(包括Int)的函数都可以工作。

既然你说你可以投掷,这是一个合理的默认函数:

{ a: Any => throw new AssertionError(s"shouldn't have looked up $a in the environment") }

假设系统的其他一些组件正在确保如果传递给eval 的树中有Var 表达式,那么环境中总是有一个适当的条目,这可能是最诚实的做法:某事在你的系统中重要的不是保持一个不变量,所以试图在你的系统中推理它可能只会让事情变得更糟。

该函数之所以有效,是因为它具有 Any => Nothing 类型,它是 String => Int 的子类型:您可以将 String 传递给它(StringAny 的子类型)并且它永远不会有结果不是IntInt 的子类型(它没有结果)。

作为替代方案,您也可以使用PartialFunction.empty 作为默认值,如果它被调用,它将抛出MatchError。这适合使用部分函数文字(这是一个裸 { case... } 块)。

所以我会选择其中一个(两者都有type Env = String => Int

val emptyEnv: Env = { a: Any => throw new AssertionError(s"shouldn't have looked up $a in the environment") }

val emptyEnv: Env = PartialFunction.empty

然后将eval定义为:

def eval(tree: Tree, env: Env = emptyEnv): Int

作为旁注,我强烈建议制作Tree sealed

sealed abstract class Tree

可以定义扩展 Tree 的类的哪些限制,并为您提供更强的保证,即强制执行有关 Trees 的不变量。

【讨论】:

  • 感谢您分享这些示例!
猜你喜欢
  • 1970-01-01
  • 2015-07-27
  • 1970-01-01
  • 2011-05-01
  • 1970-01-01
  • 2016-05-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多