“PartialFunction extends Function”是否违反了LSP原则?

6
里氏替换原则指出:
如果 ST 的一个子类型,那么可以用类型为 S 的对象代替类型为 T 的对象,而不会改变程序的任何良好属性。
然而,在 Scala 中,PartialFunction 并非适用于所有情况/定义。
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(class java.lang.String的实例)

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

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

   at ...

fnpf都是Function1的子类型,但我不能将fn替换为pf而不改变我的program。因此,在我看来,这是LSP的一种违反。

你怎么看?


这将主要是一篇观点文章。您是否有关于更一般问题的应用或使用的更具体问题? - wheaties
绝对不是。我只是向其他开发人员征求建议。也许我应该在另一个社区发布它? - gervais.b
1
您还可以定义一个Function1,对于除了“one”之外的所有输入都会抛出异常。您违反LSP的论点是抛出异常可能是一种不良的改变,但是Function1仍然可以具有抛出异常的输入。例如BigDecimal(“abc”)PartialFunctionFunction1之间的主要区别在于您内置了一种检查元素是否已定义的方法。 - Michael Zajac
没有任何编程语言可以防止用户的错误。如果有人想要破坏他的程序,他总是可以做到。我的担忧在于,我认为Scala(这种语言本身)正在违反LSP原则。这并不会使它变得不那么“酷”,但我只是在想。 - gervais.b
2个回答

6
fnpf不是Function1的子类型,因为它们根本就不是类型。它们是值,LSP并不是说你可以用T类型的任何对象替换S类型的任何对象:当然,IntInt的子类型,但将1替换为2可能会使一个正确的程序变得不正确。 PartialFunction[String, Int]Function1[String, Int]的子类型,但Function1的契约并不禁止apply抛出异常,事实上明确允许它:
应用此函数体到参数上。它可能会抛出异常。
因此,如果您的程序可以处理Function1[A,B]类型的对象,则必须以某种方式处理apply已经抛出的异常,并且PartialFunction抛出MatchError只是一种特殊情况。

1
根据维基百科,LSP需要有附加条款才能适用。

子类型的方法不应该抛出新的异常,除非这些异常本身是由超类型方法抛出的异常的子类型。

因此(假设维基百科正确),没有PartialFunction违反LSP,因为它不适用于这种情况。
编辑:Scaladoc中有其他信息:(Scaladoc Function1

请注意,Function1未定义总函数,尽管部分功能的存在可能暗示这一点。 Function1和PartialFunction之间的唯一区别在于后者可以指定它不会处理的输入。

因此,另一种思考方式是pf不是fn的子类型。
fn:String => Int pf:String的子集 => Int
进一步编辑以回应评论:
不,我的论点是LSP根本不适用。对于LSP,S不得抛出新异常。这里的S确实会抛出新异常,因此LSP无法被违反,因为它不适用。

但是PartialFunction会抛出scala.MatchError异常,而Function不会?或者您认为Function可以抛出任何异常吗?当然,异常在这里并没有真正地体现在类型中,所以两者都有可能。 - Bergi
我加入了一些新的东西。然而,我的类型理论基础知识几乎不存在,所以我非常愿意接受完全错误的可能性。 - A Spoty Spot
啊,我理解你的引用是指LSP被违反了,而不是根本没有应用。 - Bergi
我不同意:1. 这个条款已经被满足了,因为 Function1#apply 确实可以抛出任何异常。由于 Scala 没有检查异常,它可能被违反的唯一方式是如果它被记录为不会抛出异常,但它明确允许这样做。 - Alexey Romanov
1
这些并不是LSP适用的条件,而是必须满足的条件(必要但不充分)。也就是说,如果这个条款没有得到满足,那么它将违反LSP。 - Alexey Romanov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接