Why is PartialFunction <: Function in Scala?

48

在Scala中,PartialFunction[A, B]类是从类型Function[A, B]派生而来(参见Scala参考手册12.3.3)。然而,这似乎与我的直觉相反,因为一个Function(需要对所有的A进行定义)具有比PartialFunction更严格的要求,而PartialFunction可以在某些地方未定义。

我遇到的问题是当我有一个部分函数时,我不能使用Function来扩展这个部分函数。例如,我不能这样做:

(pf orElse (_)=>"default")(x)

(希望语法至少大体正确)

为什么要反向进行子类型化?我是否忽略了一些原因,例如 Function 类型是内置的事实?

顺便说一句,如果 Function1:> Function0 ,那么在上面的示例中我就不需要虚拟参数了 :-)

编辑以澄清子类型化问题

通过查看两个示例可以强调两种方法之间的区别。 哪一个是正确的?

第一个:

val zeroOne : PartialFunction[Float, Float] = { case 0 => 1 }
val sinc = zeroOne orElse ((x) => sin(x)/x) // should this be a breach of promise?

二:

def foo(f : (Int)=>Int) {
  print(f(1))
}
val bar = new PartialFunction[Int, Int] {
  def apply(x : Int) = x/2
  def isDefinedAt(x : Int) = x%2 == 0
}
foo(bar) // should this be a breach of promise?

这个问题已经得到解答了吗,还是仍然未解决? - James Iry
我不知道。我正在考虑关闭它,因为我觉得它在某个邮件列表或其他地方会更开心一些... - jpalecek
2个回答

34

因为在Scala(就像在任何图灵完备的语言中)没有保证函数是total的。

val f = {x : Int => 1 / x}

该函数在0处未定义。PartialFunction只是一个承诺告诉你它在哪里未定义的函数。不过,Scala使得实现你想要的很容易。

def func2Partial[A,R](f : A => R) : PartialFunction[A,R] = {case x => f(x)}

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

val g = pf orElse func2Partial{_ : Int => "default"}

scala> g(1)
res0: String = one

scala> g(2)
res1: String = default

如果您愿意,您可以将func2Partial设置为隐式。


2
如果PartialFunction真的承诺告诉它未定义的地方,那么这一点就是有效的。然而,我并不认为这是真的。参考资料说PartialFunction是在某些点上未定义的函数。我不认为它意味着像{ case x:Int => 1/x }这样的函数是无效的(因为它未能履行承诺),只是异常是其预期行为。 - jpalecek
5
严格来说,如果定义域包括0,则f(x) = 1/x是一个偏函数。由于Scala的类型系统不允许使用“除0以外的整数”,因此我编写的函数是一个偏函数。它只是没有一个方法来告诉调用者它在哪里定义和未定义,因此它不是一个PartialFunction对象。 - James Iry
但是Scala不要求函数是完全的,也不要求部分函数在它说它被定义时产生值。我不理解的是,你说PF做出了承诺;而我认为它制定了限制。你能提供一些指针吗?顺便问一下,部分函数{ case x:Int => 1/x }是否有效? - jpalecek
7
Scala无法强制你在isDefinedAt方法中不撒谎。因此,Scala认为你的PartialFunction是“有效”的,但用户可能会感到恼火,因为你没有按照“正确”的方式执行它。示例代码展示了一个PartialFunction对象pf,它将非零整数映射到它们的倒数。如果输入是字符串或0,则该函数未定义。 - James Iry
1
你一直告诉我关于“谎言”,但我仍然看不到Scala规范在哪里说一个PartialFunction在它的isDefinedAt()点上抛出异常是在撒谎。例如,在关于try表达式的段落中,参考文献说:“...处理程序应符合类型PartialFunction [Throwable,pt] ...”那么,像“尝试某事捕获{ case x:SomeException => throw OtherException }”这样的东西是否无效,因为处理程序在给定其域的点处抛出异常?或者,在什么情况下函数足够“定义”,以至于它不是“谎言”? - jpalecek
7
在计算机科学中,任何能够对某些输入抛出异常的函数都是部分函数 *。即使是你应该将异常转换为其他东西,但实际上却抛出了另一个异常的函数也是如此。这不一定是坏事,可能是正确的选择。目前我认为你并不想要得到原来问题的答案,而只是想辩论设计选择。请访问Scala-debate邮件列表,团队会倾听。
  • 作为一般规则,像OOM这样随时可能发生的异常,在这种讨论中被忽略。
- James Iry

19

PartialFunction有一些Function1没有的方法,因此它是子类型。这些方法是isDefinedAtorElse

你真正的问题是有时候不能推断出PartialFunction,尽管你真的希望它们能够推断出来。我希望在未来的某个时候可以解决这个问题。例如,下面的代码不起作用:

scala> val pf: PartialFunction[String, String] = { case "a" => "foo" }
pf: PartialFunction[String,String] = <function>

scala> pf orElse { case x => "default" }
<console>:6: error: missing parameter type for expanded function 
((x0$1) => x0$1 match { case (x @ _) => "default" })

但是这个可以:
scala> pf orElse ({ case x => "default" } : PartialFunction[String,String])
res5: PartialFunction[String,String] = <function>

当然,你总可以这样做:

scala> implicit def f2pf[T,R](f: Function1[T,R]): PartialFunction[T,R] = 
  new PartialFunction[T,R] { 
    def apply(x: T) = f(x)
    def isDefinedAt(x: T) = true 
  }
f2pf: [T,R](f: (T) => R)PartialFunction[T,R]

现在你更希望的是:
scala> pf orElse ((x: String) => "default")
res7: PartialFunction[String,String] = <function>

scala> println(res7("a") + " " + res7("quux"))
foo default

1
抱歉,我不认同“PartialFunction具有Function1没有的方法,因此它是子类型”的论点。这就像说,如果你有一个带有加法、乘法和相等性的EvenInteger类,以及一个带有加法、乘法、相等性和额外方法isEven的Integer类(两者都是不可变的),那么额外的方法使得Integer<:EvenInteger,而实际上恰恰相反。 - jpalecek
7
我并不反对它可以双向建模,但我不明白为什么你认为一个方向显然比另一个更合理。实际上,函数比部分函数更常见得多,由于它们都是特性,每个使用它们的类中都重复了代码。因此,从工程角度来看,部分函数应该是函数的特化,而不是相反。但如果它们能够更容易地互换使用,那会很好。 - psp

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