Type Inference fails when passing Anon Partial Function to overloaded method taking a Function

141 views
Skip to first unread message

Kai Chen

unread,
May 8, 2015, 1:13:27 AM5/8/15
to scala...@googlegroups.com
Hi,

Take the following example (scala 2.11.6)

object Partial {
  def ok1 (f: Int => Int): Int = f(0)
  def not1 (f: Int => Int): Int = f(0)
  def not1 (i: Int): Int = i
}

import Partial._

ok1 { case i => i + 1 }  // 1. works because the method is not overloaded, and the partial fun is promoted to Int=>Int
not1 { case i => i + 1 } // 2. fails with (SLS 8.5), apparently because it's overloaded, even though it should be resolved by the argument type
not1 { i => i match { case i2 => i2 + 1 } }  // 3. this works, because it's a Int=>Int

Seems that situation 2 should compile also, even though the partial function itself is not typed, the method argument should provide sufficient clue to complete it.  Is this a bug or expected behavior?

I can't seem to find anything on the issue tracker that's related.  Sorry if this is a known issue and I missed it -- and also let me know because I'd love to learn how to fix it.

Cheers,
Kai

p.s. This came up in a discussion on the Reactive Programming course forum on Coursera.  And it's also reported in RxScala https://github.com/ReactiveX/RxJava/issues/1411


Jason Zaugg

unread,
May 8, 2015, 1:43:43 AM5/8/15
to Kai Chen, scala-user
On Fri, May 8, 2015 at 1:58 PM, Kai Chen <sean.k...@gmail.com> wrote:
Hi,

Take the following example (scala 2.11.6)

object Partial {
  def ok1 (f: Int => Int): Int = f(0)
  def not1 (f: Int => Int): Int = f(0)
  def not1 (i: Int): Int = i
}

import Partial._

ok1 { case i => i + 1 }  // 1. works because the method is not overloaded, and the partial fun is promoted to Int=>Int
not1 { case i => i + 1 } // 2. fails with (SLS 8.5), apparently because it's overloaded, even though it should be resolved by the argument type
not1 { i => i match { case i2 => i2 + 1 } }  // 3. this works, because it's a Int=>Int

Seems that situation 2 should compile also, even though the partial function itself is not typed, the method argument should provide sufficient clue to complete it.  Is this a bug or expected behavior?

I can't seem to find anything on the issue tracker that's related.  Sorry if this is a known issue and I missed it -- and also let me know because I'd love to learn how to fix it.

Minor nitpick: this is a pattern matching anonymous function, it is somewhat misleading to describe it as “partial” in this context. A patten matching anon function will be either a FunctionN or a PartialFunctionN, depending on the expected type under which it is typechecked.

If multiple overloads are still vying for resolution when it comes time to typecheck the arguments, overload resolution proceeds as described in 6.2.3. The “shape type” of the arguments are computed, which lets us approximate the type of function literals in order to discard alternatives to which they would not conform.

Then, if multiple alternatives remain, the argumetnts are typechecked without an expected type. The computed types of the arguments are then checked for compatibility with the formal parameter types of each alternative (compatibility = type conformance or implicit convertability). If multiple alternatives still exists, the most specific one is chosen. It is an error if no most specific alternative exists.

In your example, no special rule exists for the "shape type" of pattern matching anonymous functions. It is not possible to approximate the type of these. For example: the same syntax can be used to create functions of different arities because of the transparent handling of tuples/param lists:

scala> val f: Function2[Int, Int, String] = { case (x, y) => "" }
f: (Int, Int) => String = <function2>

scala> val g: Function1[(Int, Int), String] = { case (x, y) => "" }
g: ((Int, Int)) => String = <function1>

As a further example:

scala> val g: Function3[Int, Int, Int, String] = { case _ => "" }
g: (Int, Int, Int) => String = <function3>

scala> val g: PartialFunction[Int, String] = { case _ => "" }
g: PartialFunction[Int,String] = <function1>

scala> val g: Function1[Any, String] = { case _ => "" }
g: Any => String = <function1>

The best we could say is that the “shape type” of a pattern matching anonymous function is Function0[_] | Function1[_, _].... | PartialFunction[_, _]. That would be sufficient to exclude the wrong overload and make your example compile. But one would also need to specify the meaning of | (a union type). We tend to take the stand that overloading is a second class feature in Scala, so we'll resist further spec or implementation complexity here, even if they offer incremental improvements.

Interestingly enough, Java 8’s overload resolution does typecheck all the alternatives and could handle this. I was playing around with this the other day (and can’t figure out why the last line doesn’t compile in that gist.

Java can probably afford to pay the cost of this brute force overload resolution because it doesn’t require implicit searches for checking compatibility. They aren’t without the odd pathological performance case around overloading, though, and, AFAICT, are planning to rework the typechecker under the banner of JEP 215 to speed them up.

-jason

Kai Chen

unread,
May 8, 2015, 2:09:32 AM5/8/15
to scala...@googlegroups.com, sean.k...@gmail.com
Got it!  Not a minor nitpick at all -- it's really the root-cause.  Good to know that a 'pattern matching anon fun' can be so amorphous.

Thanks for the quick reply!

Dale Wijnand

unread,
May 8, 2015, 3:09:07 AM5/8/15
to Kai Chen, scala...@googlegroups.com

What a great explanation. Thank you Jason.


On Fri, 8 May 2015 07:09 Kai Chen <sean.k...@gmail.com> wrote:
Got it!  Not a minor nitpick at all -- it's really the root-cause.  Good to know that a 'pattern matching anon fun' can be so amorphous.

Thanks for the quick reply!

--
You received this message because you are subscribed to the Google Groups "scala-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-user+...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Som Snytt

unread,
May 8, 2015, 12:36:24 PM5/8/15
to Jason Zaugg, Kai Chen, scala-user
For the gist ambiguity, it looks like there are additional rules in 15.12.2.5 for explicitly typed lambdas, whereby the result of the lambda param assists overload res.

In this case, the void result of Consumer makes it less specific. But you lose that assistance if the expression is implicitly typed, so it's ambiguous.

It will similarly prefer a primitive result to reference type, such as f(IntUnaryOperator) over f(IntFunction<Integer>), if the expr result is primitive.

Reply all
Reply to author
Forward
0 new messages