Better type inference for Scala: send us your problematic cases!

Ah right, I missed that, so good news: your original program already works as is in Dotty :).

1 Like

Good news :slight_smile:

As a mere Scala user (haven’t followed Dotty dev closely) I would like to know if Dotty:

  1. Still requires the use of the Aux pattern
  2. If we still need to use currying so that inference can work in a function/method’s parameters and if the left to right rule still applies
  3. I we still have “type erasure” issues when dealing with Scala code only (no mixing Java)

Finally are you also interested in examples that may involve (for example Shapeless) macro magic?

TIA

You can now refer to other parameters in the same parameter list, which should make the Aux pattern more or less obsolete. But the type inference doesn’t seem to work yet though…

trait Foo[In] { type Out }
trait Bar[In]

implicit def fooInt: Foo[Int]{ type Out = String } = ???
implicit def fooString: Foo[String]{ type Out = Boolean } = ???

implicit def barInt: Bar[Int] = ???
implicit def barBoolean: Bar[Boolean] = ???


def works[A, B, C](implicit f1: Foo[A] { type Out = B }, f2: Foo[B] { type Out = C }, b: Bar[C]): C = ???
// compiles, inferred a Boolean
works[A = Int]

def fails[A](implicit f1: Foo[A], f2: Foo[f1.Out], b: Bar[f2.Out]): f2.Out = ???
// doesn't compile: no implicit argument of type Foo[f1.Out] was found for parameter f2 of method fails
fails[Int] 
1 Like

@Jasper-M Thanks for the info. Hope that second ‘fails’ example will work in the future. Much cleaner syntax.

In case of Right(3) and "something" the common type is java.io.Serializable, so I think the issue is with choosing the candidate of type inference, not the subclassing itself.

As per interfacing the dynamic languages, for example JavaScript, the benefit of using languages like Scala.JS and TypeScript is because they have stronger type system to check programming errors, not because it’s permissible to Option(2) getting mixed up with Int. If people wanted that, they would use JavaScript itself. In situations where List[Any] is actually wanted, we can ask people to write Option(2): Any.

This should also improve situations such as List(1, 2, 3).contains("wat") - Type-safe contains.

In Scala 2.x that would’ve caused too much false positives for ADTs, but thanks to Eq, I don’t think it’s that far fetched of an idea.

2 Likes

Here is another example where scalac gives a compilation error: https://scastie.scala-lang.org/n8Oxft9xSVCmfg2gii87Vw. It seems that dotc already compiles that code, but do you think scalac could be fixed too?

What about this sort of thing where the method call in yield determines the type parameters for the preceding decode calls?

2 Likes

(IMPROVED EXAMPLE, see new link below)

EDIT REDUX: My apologies for the hostile tone of my previous edit. I was having a rough day and took much greater offense than was warranted at my post being flagged as off-topic. Please indulge me in one final revision.

I have an example of some code which fails to compile, I believe due to a failure in type inference. It would have been difficult to eliminate the dependency on lift-json, because the failure seems to depend on some tricky implicits defined in their DSL for constructing JSON.

This Scastie snippet is where the code itself can be found. It now has the dependency which I previously had difficulty adding.

Note that if the second List is replaced with List[JObject] (as indicated in the code comments), it does compile.

This: https://github.com/scala/bug/issues/10830

abstract class A(i: Int)
val a = new A(_){}

Which complains with missing parameter type for expanded function.

@hmf

Thanks for brining this up, I’ve opened an issue to track this: implicit search fails with intra-parameter-list dependency · Issue #5427 · lampepfl/dotty · GitHub

Currying no longer influences inference: we’ll always try to infer things as late as possible, for more details see the slides and talks I linked to in my initial post.

Dotty still erases types, this is a pretty fundamental aspect of the language.

If the examples are about the interaction between inference and macros, then there’s probably not much actionable information I can get from them, but if you can manage to simplify them to the points where the macros can be replaced by dummy methods, then yes.

Probably, but you’ll need to find someone to actually fix it :).

@steven-collins-omega The way lift-json construct values by doing implicit conversions everywhere makes it very hard for type inference to figure out what it should be doing, since it can’t rely on the expected type. To avoid adding extra annotations, you could replace List by JList with JList defined a bit like this (I don’t know much about lift-json so no clue if this is a good idea in practice, but it works for your example):

object JList {
  def apply(values: JValue*) = List(values)
}

@ollijh Because for expressions desugar to map/flatMap/… calls, what you’re asking for would be equivalent to having decode("123").map(x: Int => x) infer intDecoder because the map call happens to only work in that case. This would be impossible to do in general since the result of the implicit search can influence the type of the following .map, and thus the relationship between the type of the argument passed to map and the result type of the decode call. In a nutshell, I’m afraid that because for-expressions are not restricted enough to let us improve type inference in this case, but maybe I’m overlooking something ?

1 Like

@Lasering As discussed in the linked issue and the linked gitter discussion in the issue, this is doable, but it’s not clear that the extra complexity in the implementation would be worth it since this is such an edge case.

@smarter, do you think this could work in Dotty?

import cats.arrow.Arrow
import cats.implicits._

val f = ((_:Int) + 1) *** ((_:Int) * 2)

(10,20) |> f // ok

(10,20) |> (_ + 1) *** (_ * 2) // missing parameter type for expanded function
1 Like

Here’s a test case you might want to include: it works in dotty, but not in scalac: https://scastie.scala-lang.org/Q3yotmoGTKOAsyc63COh4Q

Works as of this PR.

1 Like

@LPTK I think this is equivalent to:

(x => x)(1)

All I can say without looking deeper into it is that it doesn’t seem impossible, maybe open an issue so we keep track of that ?

@lrytz PRs adding test cases are more than welcome!

@smarter no it’s not. The following works well in all versions of Scala:

implicit class Helper[A](self: A) {
  def |> [B] (rhs: A => B): B = rhs(self)
}
1 |> (x => x)