`PartialFunction extends Function` 是否违反了 LSP?

Is `PartialFunction extends Function` a violation of LSP?

里氏替换原则指出

if S is a subtype of T, then objects of type T may be replaced with objects of type S without altering any of the desirable properties of that program.

然而,在 Scala 中,有一个 PartialFunction 在所有情况下都不是 applicable/defined。

trait Function1 {
  def apply(x: A): R
}

trait PartialFunction extends Function1 {
  def apply(x: A): R
  def isDefinedAt(x: A): Boolean
}

如果将 PartialFunction 应用于未定义的值,您将收到异常。

在 Scala 中创建 PartialFunction 的一种便捷方法是使用模式匹配。这样做,您会收到一个 MatchError 表示未定义的值。

val fn:Function1[String, Int] = s => s.length

val pf:PartialFunction[String, Int] = {
  case "one" => 3
}

def program(f:Function1[String, Int], s:String):(Boolean, Int) = (
  f.isInstanceOf[Function1[String, Int]], f(s)
)

program(fn, "one") == program(pf, "one")
program(fn, "two") == program(pf, "two")

fn: String => Int = <function1>

pf: PartialFunction[String,Int] = <function1>

program: program[](val f: String => Int,val s: String) => (Boolean, Int)

res0: Boolean = true

scala.MatchError: two (of class java.lang.String)

   at scala.PartialFunction$$anon.apply(delme.sc:249)

   at scala.PartialFunction$$anon.apply(delme.sc:247)

   at ...

fnpf 都是 Function1 的子类型,但我不能在不改变我的 program 的情况下用 pf 替换 fn。所以在我看来这是违反LSP的。

你怎么看?

根据维基百科,申请 LSP 需要一个附加条款。

No new exceptions should be thrown by methods of the subtype, except where those exceptions are themselves subtypes of exceptions thrown by the methods of the supertype.

因此(假设维基百科是正确的),没有任何 PartialFunction 不违反 LSP,因为它不适用于这种情况。

编辑:scaladoc 中还有其他信息:(Scaladoc Function1)

Note that Function1 does not define a total function, as might be suggested by the existence of PartialFunction. The only distinction between Function1 and PartialFunction is that the latter can specify inputs which it will not handle.

所以另一种思考方式是 pf 不是 fn 的子类型。

fn: 字符串 => 整数

pf: String 的子集 => Int

进一步编辑以回应评论: 不,我是说 LSP 根本不适用。对于应用 S 的 LSP 不得抛出新的异常。 S 这里确实会抛出新的异常,因此不会违反 LSP,因为它不适用。

fnpf 不是 Function1 的子类型,因为它们根本不是类型。它们是值,LSP 并没有说你可以将任何 T 类型的对象替换为任何 S 类型的对象:Int 肯定是 [=15= 的子类型],但是用2替换1会使正确的程序出错。

PartialFunction[String, Int]Function1[String, Int] 的子类型,但 Function1 的契约并不禁止 apply 抛出异常,事实上明确允许它:

Apply the body of this function to the argument. It may throw an exception.

因此,如果您的程序可以处理 Function1[A, B] 类型的对象,它必须已经以某种方式处理 apply 抛出异常,而 PartialFunction 抛出 MatchError 是只是一个具体案例。