【问题标题】:Is `PartialFunction extends Function` a violation of LSP?`PartialFunction extends Function` 是否违反了 LSP?
【发布时间】:2017-03-17 02:47:41
【问题描述】:

里氏替换原则指出

如果ST 的子类型,则T 类型的对象可以替换为S 类型的对象,而不会改变该程序的任何所需属性。

但是,在 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 =

pf: PartialFunction[String,Int] =

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

res0: Boolean = true

scala.MatchError: 两个(属于 java.lang.String 类)

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

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

  在 ...

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

你怎么看?

【问题讨论】:

  • 这将主要是一篇评论文章。对于您所问的更一般性问题的应用或使用,您有更具体的问题吗?
  • 绝对不是。我只是向其他开发人员征求意见。也许我应该把它发布到另一个社区?
  • 您还可以定义一个Function1,它只为除"one" 之外的所有输入抛出异常。您违反 LSP 的论点是抛出异常可能是一种不受欢迎的改变,但 Function1 仍然可以有抛出异常的输入。例如BigDecimal("abc")PartialFunctionFunction1 之间的主要区别在于您有一个内置的方法来检查是否首先定义了元素。
  • 没有语言可以防止用户出错。如果有人想破坏他的程序,他总是可以做到的。我的担忧不仅仅是在我看来,Scala(语言本身)违反了 LSP。这永远不会让它变得不那么“酷”,但我只是想知道。

标签: scala liskov-substitution-principle


【解决方案1】:

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

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

将此函数的主体应用于参数。 可能会抛出异常。

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

【讨论】:

    【解决方案2】:

    根据维基百科,LSP 需要一个附加条款才能应用。

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

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

    编辑:scaladoc 中有附加信息:(Scaladoc Function1)

    请注意,Function1 没有定义一个总函数,PartialFunction 的存在可能暗示了这一点。 Function1 和 PartialFunction 之间的唯一区别是后者可以指定它不会处理的输入。

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

    fn: 字符串 => 整数

    pf: String 的子集 => Int

    针对评论的进一步编辑: 不,我认为 LSP 根本不适用。应用 S 的 LSP 不能抛出新的异常。这里的 S 确实抛出了新的异常,因此 LSP 不能被违反,因为它不适用。

    【讨论】:

    • 但是PartialFunction 会抛出scala.MatchErrorFunction 不会?还是您在争论 Function 可以扔任何东西?当然,这里的类型并没有真正表达异常,所以它也可能是。
    • 我添加了更多内容。我对类型理论基础的知识几乎不存在,所以我很容易完全错误。
    • 啊,我理解你的引用是因为 LSP 被 violated 这种异常行为,而不是根本不适用。
    • 我不同意: 1.这个条款满足的,因为Function1#apply确实可以抛出任何异常。由于 Scala 没有检查异常,因此违反它的唯一方法是如果它记录不抛出,但它是明确允许的。
    • 2.这些根本不是 LSP 申请的条件,而是持有(必要,但不充分)。 IE。如果不满足该条款,则违反 LSP。
    猜你喜欢
    • 2013-06-15
    • 1970-01-01
    • 1970-01-01
    • 2018-10-06
    • 1970-01-01
    • 1970-01-01
    • 2017-02-11
    • 1970-01-01
    相关资源
    最近更新 更多