Why is PartialFunction <: Function in Scala?
Asked Answered
I

2

48

In Scala, the PartialFunction[A, B] class is derived from type Function[A, B] (see Scala Reference, 12.3.3). However, this seems counterintuitive to me, since a Function (which needs to be defined for all A) has more stringent requirements than a PartialFunction, which can be undefined at some places.

The problem I've come across is that when I have a partial function, I cannot use a Function to extend the partial function. Eg. I cannot do:

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

(Hope the syntax is at least remotely right)

Why is this subtyping done reversely? Are there any reasons that I've overlooked, like the fact that the Function types are built-in?

BTW, it would be also nice if Function1 :> Function0 so I needn't have the dummy argument in the example above :-)

Edit to clarify the subtyping problem

The difference between the two approaches can be emphasized by looking at two examples. Which of them is right?

One:

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

Two:

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?
Iulus answered 30/5, 2009 at 21:51 Comment(2)
Has this question been answered or is it still open?Rapeseed
I don't know. I'm considering closing it, because I think it would be happier in some mailing list or something...Iulus
R
34

Because in Scala (as in any Turing complete language) there is no guarantee that a Function is total.

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

That function is not defined at 0. A PartialFunction is just a Function that promises to tell you where it's not defined. Still, Scala makes it easy enough to do what you want

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

If you prefer, you can make func2Partial implicit.

Rapeseed answered 30/5, 2009 at 23:22 Comment(6)
That point would be valid, if PartialFunction would really make a promise to tell where it's undefined. However, I'm not convinced this is really true. The Reference says a PartialFunction is a Function undefined at some points. I don't think it means a function like { case x:Int => 1/x } is invalid (because it fails to fulfill the promise), just that the exception is its intended behaviour.Iulus
Formally speaking f(x) = 1/x is a partial function if the domain includes 0. Since Scala's type system doesn't let you say "Int except 0" then what I wrote is a partial function. It's just not a PartialFunction object because it doesn't have a method to tell callers where it is and isn't defined.Rapeseed
But Scala doesn't require a function to be total, nor does it require a partial function to yield value when it says it's defined. What I have trouble with understanding is that you say a PF makes a promise; while I think it makes a restriction. Could you provide some pointers on that? BTW, is the partial function { case x:Int => 1/x } valid or not?Iulus
Scala can't force you to not lie in your isDefinedAt method. So Scala considers your PartialFunction "valid" but users will be irritated that you didn't do it "properly." scala> val pf : PartialFunction[Any,Int] = {case x : Int if x != 0 => 1/x} pf: PartialFunction[Any,Int] = <function> scala> pf.isDefinedAt("hello") res2: Boolean = false scala> pf.isDefinedAt(0) res3: Boolean = false scala> pf.isDefinedAt(1) res4: Boolean = trueRapeseed
You keep telling me about "lies" but I still don't see where does the Scala spec say that a PartialFunction throwing an exception on a point where it isDefinedAt() is lying. For example, in a paragraph about try-expressions, the Reference says "... the handler is expected to conform to type PartialFunction[Throwable, pt] ..." So, is something like "try something catch { case x:SomeException => throw OtherException }" invalid because the handler throws an exception given a point from its domain? Or, in what case is the function "defined" enough that it's not "lying"?Iulus
In CS any function that can throw an exception for some inputs is partial*. That goes even for your function that should be translating an exception into something but is instead throwing another exception. That's not automatically a bad thing. It might be the right choice. At this point I'm getting the impression that you don't want your original question answered - you just want to debate the design choice. Please visit the scala-debate mailing list, the team listens. * As a general rule things like OOM which can happen anywhere and any time are ignored for this kind of discussion.Rapeseed
A
19

PartialFunction has methods which Function1 does not, therefore it is the subtype. Those methods are isDefinedAt and orElse.

Your real problem is that PartialFunctions are not inferred sometimes when you'd really like them to be. I'm hopeful that will be addressed at some future date. For instance this doesn't work:

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" })

But this does:

scala> pf orElse ({ case x => "default" } : PartialFunction[String,String])
res5: PartialFunction[String,String] = <function>

Of course you could always do this:

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]

And now it's more like you want:

scala> pf orElse ((x: String) => "default")
res7: PartialFunction[String,String] = <function>

scala> println(res7("a") + " " + res7("quux"))
foo default
Airlift answered 3/6, 2009 at 16:37 Comment(2)
Sorry, but I don't buy the "PartialFunction has methods which Function1 does not, therefore it is the subtype" argument. It's like saying that if you have a class EvenInteger with addition, multiplication and equality, and a class Integer with addition, multiplication, equality and additional method isEven (both immutable), the additional method makes Integer <: EvenInteger, while in fact, it's just the opposite.Iulus
I don't disagree that it could be modeled in either direction, but I'm not really seeing why you think one direction is obviously more sensible than the other. In practice functions are many many times more common than partial functions, and since they're both traits the code is duplicated in every class which uses them. So there are also engineering reasons PF should be a specialization of F1 and not the other way around. But it would be nice if they were more easily interchangeable.Airlift

© 2022 - 2024 — McMap. All rights reserved.