`PartialFunction是否扩展了函数`违反了LSP?

时间:2016-11-03 13:32:36

标签: scala liskov-substitution-principle

Liskov Substitution Principle声明

  

如果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 =< function1>

     

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

     

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

     

res0:Boolean = true

     

scala.MatchError:两个(类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。

您怎么看?

2 个答案:

答案 0 :(得分:4)

fnpf不是Function1的子类型,因为它们根本不是类型。它们是值,LSP并不表示您可以使用T类型的任何对象,并将其替换为S类型的任何对象:Int肯定是{{Int的子类型1}},但用1替换2会使正确的程序不正确。

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

  

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

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

答案 1 :(得分:0)

根据维基百科,LSP需要附加条款。

  

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

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

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

  

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

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

fn:String => INT

pf:String的子集=> INT

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