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

I think you need to make that:

inline def [A, B >: A](elem: B) :: (ls: List[A]): List[B] = NonEmptyList(elem, ls) 
1 Like

Works. Thank you!

This was working prior to 0.24:

trait P[M: Ordering, C] {
  def apply(s: (M, C)*): Seq[(M, C)]
  def sort(x: Seq[(M, C)]) = apply(x.sortBy((s, _) => s): _*)
                                             ^
                 cannot infer type; expected type <?> is not fully defined
}

Can you open an issue for that? Also I suspect it can be minimized further (the use of a lambda whose expected type comes from a repeated parameter is probably the important part here)

6 posts were split to a new topic: Scala 2 type inference questions

This thread is specifically about improving Dotty’s type inference. For Scala 2 issues, I suggest opening an issue on github.com/scala/bug/issues

3 Likes

The following shapeless-style derivation of a polymorphic sequence operation for Option doesn’t infer a useful type in Scala 3.0.0-M1. The type is checkable but on failure the expected type is not useful.


object Blah {

  trait Options[T <: Tuple] {
    type Out <: Tuple
    def apply(t: T): Option[Out]
  }

  object Options {

    type Aux[T <: Tuple, O <: Tuple] = Options[T] { type Out = O }

    implicit def single[H]: Aux[Option[H] *: EmptyTuple, H *: EmptyTuple] =
      new Options[Option[H] *: EmptyTuple] {
        type Out = H *: EmptyTuple
        def apply(t: Option[H] *: EmptyTuple): Option[H *: EmptyTuple] =
          t.head.map(_ *: EmptyTuple)
      }

    implicit def multiple[H, T <: Tuple](
      implicit ev: Options[T]
    ): Aux[Option[H] *: T, H *: ev.Out] =
      new Options[Option[H] *: T] {
        type Out = H *: ev.Out
        def apply(t: Option[H] *: T) =
          (t.head.flatMap(h => ev(t.tail).map(t => h *: t)))
      }

  }

  extension [T <: Tuple](t: T)(using ev: Options[T]) {
    def sequence: Option[ev.Out] = ev(t)
  }

  // If we annotate this with an incorrect type it fails to typecheck and gives the inferred
  // type as Option[?1.Out] rather than the expected fully-expanded type.
  val x: Option[(Int, String, Boolean, Double)] =
    (Option(1), Option("x"), Option(true), Option.empty[Double]).sequence

}

Is there a better way to do this?

Maybe the pretty-printer could try harder to get rid of these path-dependent things (bug report welcome), but if we avoid the Aux pattern and just use type parameters the problem goes away too:

object Blah {
  trait Options[T <: Tuple, Out] {
    def apply(t: T): Option[Out]
  }
  // one can always define `type Foo[T <: Tuple] = Options[T, _]`
  // to hide the parameter.

  object Options {
    implicit def single[H]: Options[Option[H] *: EmptyTuple, H *: EmptyTuple] =
      new Options[Option[H] *: EmptyTuple, H *: EmptyTuple] {
        def apply(t: Option[H] *: EmptyTuple): Option[H *: EmptyTuple] =
          t.head.map(_ *: EmptyTuple)
      }

    implicit def multiple[H, T <: Tuple, Tail <: Tuple](
      implicit ev: Options[T, Tail]
    ): Options[Option[H] *: T, H *: Tail] =
      new Options[Option[H] *: T, H *: Tail] {
        def apply(t: Option[H] *: T) =
          (t.head.flatMap(h => ev(t.tail).map(t => h *: t)))
      }
  }

  extension [T <: Tuple, Out](t: T)(using ev: Options[T, Out]) {
    def sequence: Option[Out] = ev(t)
  }

  // OK:
  val x: Option[(Int, String, Boolean, Double)] =
    (Option(1), Option("x"), Option(true), Option.empty[Double]).sequence 

  // error: Found:    Option[Int *: String *: Boolean *: Double *: EmptyTuple]
  //        Required: String
  val y: String =
    (Option(1), Option("x"), Option(true), Option.empty[Double]).sequence 
}
1 Like

This is a pretty interesting gotcha I just stumbled upon: https://scastie.scala-lang.org/8r2pKMoXRyCk1wjUYeccHQ

Inlined:

def isTrue(b: Boolean): Boolean = b
val notTrue = !isTrue(_) // Missing parameter type\n\nI could not infer the type of the parameter _$1
2 Likes

I believe that is !((b: Boolean) => b), and since Function1 has no ! method, it gets lost. The error message is deceiving. I expect you intended it to expand like this:

val notTrue = (b: Boolean) => !isTrue(b)

Which compiles fine.

Actually, the error message suggests the expansion is as intended, but then the type inference still fails:


error: missing parameter type for expanded function ((<x$1: error>) => isTrue(x$1).unary_$bang)

Equivalent case without prefix operator:

> def m(i: Int): Int = i
def m(i: Int): Int

> val isFinite = m(_).isFinite
^

error: missing parameter type for expanded function ((<x$1: error>) => m(x$1).isFinite)
2 Likes

Probably moot since we will get Enums in Dotty, but thgis is one that frequently pops up for me and I find it annoying:

trait Error
case class SomethingBadHappened(msg: String) extends Error
case object UnexpectedError extends Error
//plus a few more 

def someIO: IO[String, Thing]

def newIO: IO[Error, Thing] = someIO.leftMap(SomethingBadHappened(_)) //expected Error but got SomethingBadHappened

def newIO: IO[Error, Thing] = someIO.leftMap(SomethingBadHappened(_):Error) //compiles fine

also may be missing something with some language design principle, but this seems like a failure of the compiler to me

3 Likes

Uncommenting line 6 works, but would be nice if compiler could infer these instead of Nothing?

1 Like

I wish I could write compose as

  def compose[G[_]: Functor]: Functor[F[G[_]]] =
    Functor.make[F[G[_]]](
      [A, B] => f => fga => map(fga)(Functor[G].map(_)(f))
    )

That’s not type inference specific though, just syntax. Same for value level functions. This won’t work as well.

def compose[A, B, C](f: A => B, g: B => C): A => C = f(g(_))

Yes, for the composed type, I realised after. We could get that with a composition type operator I guess, e.g.

type ∘[F[_], G[_]] = [A] =>> F[G[A]]

But the type signature for parameters f and fga still are type-inference related, right?

Upper bound <: List[Int] on type constructor F is not satisfied by List[Int] instantiation

scala> def f[F[_ <: Int] <: List[Int], A <: Int](as: F[A]) = as
def f[F[_ <: Int] <: List[Int], A <: Int](as: F[A]): F[A]

scala> f(List(42))
       ^
       error: inferred type arguments [List,Int] do not conform to method f's type parameter bounds [F[_ <: Int] <: List[Int],A <: Int]
             ^
       error: type mismatch;
        found   : List[Int]
        required: F[A]

however the following type alias with not bound on the argument A satisfies it

scala> type Lst[A] = List[Int]
type Lst

scala> f[Lst, Int](List(42))
val res1: Lst[Int] = List(42)

Cross-posted

2 Likes

Higher-order unification in general is undecidable, so the compiler has to limit it to a reasonable subset, in particular when checking List[Int] <:< F[A] we end up checking if List <: F and Int <: A, and the first check doesn’t succeed, you can write def f[F[X <: Int] <: List[X], A <: Int](as: F[A]) = as instead.

5 Likes