4

I asked this question earlier: Combine a PartialFunction with a regular function

and then realized, that I haven't actually asked it right. So, here goes another attempt.

If I do this:

 val foo = PartialFunction[Int, String] { case 1 => "foo" }
 val bar = foo orElse { case x => x.toString }

it does not compile: error: missing parameter type for expanded function The argument types of an anonymous function must be fully known. (SLS 8.5) Expected type was: PartialFunction[?,?]

But this works fine:

   val x: Seq[String] = List(1,2,3).collect { case x => x.toString }

The question is what is the difference? The type of the argument is the same in both cases: PartialFunction[Int, String]. The value passed in is literally identical. Why one does one case work, but not the other?

Community
  • 1
  • 1
Dima
  • 39,570
  • 6
  • 44
  • 70
  • 2
    `collect` expects an `PartialFunction[A, B]` while `orElse` expects an `[A1 <: A, B1 >: B] PartialFunction[A1, B1]`, so while the compiler can infer the first, you somehow need to help with the second. – Peter Neyens Jul 13 '16 at 19:35
  • Thanks, @PeterNeyens, that explains it for me. Post it as an answer is you care about 25 points rep. bump :) – Dima Jul 13 '16 at 22:01
  • You can add @PeterNeyens answer and accept it so it can be easily found and people don't need to go through the comments as I did. – MaxNevermind Aug 06 '18 at 10:42

3 Answers3

0

You need to specify the type for bar because the compiler is unable to infer it. This compiles:

val foo = PartialFunction[Int, String] { case 1 => "foo" }
val bar : (Int => String) = foo orElse { case x => x.toString }
henrik
  • 85
  • 10
  • In this case the ```orElse``` method that gets called is in Function rather than PartialFunction. That illustrates another ambiguity around this that you wouldn't want the compiler to just assume. I'm not sure that the OP wants a Function in the end. – Scott Shipp Jul 13 '16 at 19:44
  • @ScottShipp are you sure? where is `orElse` in `Function`? And what does it do? – Dima Jul 13 '16 at 20:00
  • @henrik, I realize it is unable to infer it. The question was _why_ it is unable to infer it, and why it is able to infer it in the other case. – Dima Jul 13 '16 at 20:01
  • @Dima `orElse` is a method in the `PartialFunction` trait and has type bounds `[A1 <: A, B1 >: B]`, in this case `A` is `Int`, and `B` is `String`. Because `B1` is only lower bounded by type `String`, it can't infer the actual type that you want. – henrik Jul 13 '16 at 20:51
  • Yeah, I got that from @PeterNeyens comment earlier. Too bad he didn't post that as an answer :) – Dima Jul 13 '16 at 22:00
0

In the case of List(1,2,3).collect{case x => x.toString} the compiler is able to infer the input type of the partial function based off of how theList was typed.

final override def collect[B, That](pf: PartialFunction[A, B])(implicit bf: CanBuildFrom[List[A], B, That])

Based on the type parameters the compiler can infer that you are passing a correctly typed partial function. That's why List(1,2,3).collect{case x:String => x.toString} does not compile nor does List(1,2,3).collect{case x:Int => x.toString; case x: String => x.toString}.

Since List is covariant the compiler is able to infer that the partial function {case x => x.toString} is a partial function on Int. You'll notice that List(1,2,3).collect{case x => x.length} does not compile because the compiler is inferring that you're operating on either an Int or a subclass of Int.

Also keep in mind that the {case x => x.toString} is just syntactic sugar. If we do something like the below then your example works as expected

val f = new PartialFunction[Int, String](){
  override def isDefinedAt(x: Int): Boolean = true
  override def apply(v1: Int): String = v1.toString
}

val foo = PartialFunction[Int, String] { case 1 => "foo" }

val bar = foo orElse f //This compiles fine.

List(1,2,3).collect{f} // This works as well.

So the only logical answer from my perspective is that the syntactic sugar that is able to generate a PartialFunction instance for {case x => x.toString} does not have enough information at compile time to be able to adequately type it as a PartialFunction[Int, String] in your orElse case.

nattyddubbs
  • 2,085
  • 15
  • 24
  • This is not the perfect solution, because you did not implement `applyOrElse`. `applyOrElse` will improve the performance. – Yang Bo Sep 03 '16 at 13:22
0

You can use the library Extractor.scala.

import com.thoughtworks.Extractor._

// Define a PartialFunction
val pf: PartialFunction[Int, String] = {
  case 1 => "matched by PartialFunction"
}

// Define an optional function
val f: Int => Option[String] = { i =>
  if (i == 2) {
    Some("matched by optional function")
  } else {
    None
  }
}

// Convert an optional function to a PartialFunction
val pf2: PartialFunction[Int, String] = f.unlift

util.Random.nextInt(4) match {
  case pf.extract(m) => // Convert a PartialFunction to a pattern
    println(m)
  case f.extract(m) => // Convert an optional function to a pattern
    println(m)
  case pf2.extract(m) => // Convert a PartialFunction to a pattern
    throw new AssertionError("This case should never occur because it has the same condition as `f.extract`.")
  case _ =>
    println("Not matched")
}
Yang Bo
  • 3,586
  • 3
  • 22
  • 35