That is just a limitation of the type inference algorithm which was fixed in Scala 3.
The TL;DR; is that foldLeft
is defined like:
def foldLeft[B](z: B)(op: (B, A) => B): B
So the type checker sees that z
is Nil
which has type Nil.type
and thus it fixes the type parameter B
to that, which makes everything fail.
When you do: List[T]()
that has List[T]
as the return type which makes B
to be List[T]
and everything typechecks.
However, note that since Nil.type <: List[Nothing] <: List[T]
(for any type T
) both are equivalent and should work the same; and that is basically what the Scala 3 version of the typechecker does, it doesn’t fix B
to Nil.type
yet, but rather annotates that as a possibility and keeps reading the code before fixing it to List[T]
in the end.
BTW; List[T]()
has some additional overhead and is not that common, usually you would do: List.empty[T]
instead.
Which is defined like this:
object List {
def empty[T]: List[T] = Nil
}
Well, they are different because List()
is a method and Nil
is an object, but List()
will just return Nil
since the code for apply
is more or less this:
object List {
def apply[A](args: A*): List[A] =
if (args.isEmpty) Nil
else args.head :: List.apply(args : _*)
}
The real code may be more optimized but the end result is the same, if args
is empty it returns Nil
The important difference is just the return type.
For example if you only used List()
(without the type parameter) you would get a similar error that with Nil
Is not really an optimization, is just the definition, Nil
is the empty List
Remember that List
is a very simple structure:
// A List is either an empty list or a non-empty one.
// a non-empty one has a head of type A and a tail of type List[A]
sealed trait List[+A]
final case class Cons[+A](head: A, tail: List[A]) extends List[A]
final case object Nil extends List[Nothing]
That is all.