Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

class A has one type parameter, but type B has one

Tags:

scala

Recently I stumbled across a strange (to me) compiler error message. Consider the following code:

trait Foo {
  type Res <: Foo
  type Bar[X <: Res]
}

class MyFoo extends Foo {
  override type Res = MyFoo
  override type Bar[X <: Res] = List[X]
}

type FOO[F <: Foo, R <: Foo, B[_ <: R]] = F { type Res = R; 
                                              type Bar[X <: R] = B[X] }

def process[F <: Foo, R <: Foo, B[_ <: R]](f: FOO[F, R, B]) {}

Now, if I want to call the process method I have to explicitly write the type parameters:

process[MyFoo, MyFoo, List](new MyFoo) // fine

If I write:

process(new MyFoo)

or

process((new MyFoo): FOO[MyFoo, MyFoo, List])

I get the following error message:

inferred kinds of the type arguments (MyFoo,MyFoo,List[X]) do not conform to the expected kinds of the type parameters (type F,type R,type B). List[X]'s type parameters do not match type B's expected parameters: class List has one type parameter, but type B has one

Why isn´t the compiler able to infer the types (although I explicitly stated them at call parameter)? And what does that class List has one type parameter, but type B has one mean? Something has one, but the other has also one, and that´s why they don´t fit together???

like image 296
Peter Schmitz Avatar asked Sep 26 '12 21:09

Peter Schmitz


People also ask

How does Scala determine types when they are not specified?

For example, a type constructor does not directly specify a type of values. However, when a type constructor is applied to the correct type arguments, it yields a first-order type, which may be a value type. Non-value types are expressed indirectly in Scala.

What do you call a Scala method that is parameterized by type as well as by value?

Language. Methods in Scala can be parameterized by type as well as by value. The syntax is similar to that of generic classes.

Which one of the following options is the correct name for empty type parameter?

It is called generics.

What is a type bound?

In Scala, Type Bounds are restrictions on Type Parameters or Type Variable. By using Type Bounds, we can define the limits of a Type Variable. Scala Type Bounds give us the benefit of Type-Safe Application Development. Scala supports the following Type Bounds for Type Variables: Scala Upper Bounds.


1 Answers

If we look to the Scala compiler, the sources could help us understanding what the problem is. I have never contributed to the Scala compiler, but I found the sources very readable and I have already investigated on that.

The class responsible for type inference is scala.tools.nsctypechecker.Infer which you can find simply by looking in the Scala compiler sources for a part of your error. You'll find out the following fragment:

  /** error if arguments not within bounds. */
    def checkBounds(pos: Position, pre: Type, owner: Symbol,
                    tparams: List[Symbol], targs: List[Type], prefix: String) = {
      //@M validate variances & bounds of targs wrt variances & bounds of tparams
      //@M TODO: better place to check this?
      //@M TODO: errors for getters & setters are reported separately
      val kindErrors = checkKindBounds(tparams, targs, pre, owner)

      if(!kindErrors.isEmpty) {
        error(pos,
          prefix + "kinds of the type arguments " + targs.mkString("(", ",", ")") +
          " do not conform to the expected kinds of the type parameters "+ tparams.mkString("(", ",", ")") + tparams.head.locationString+ "." +
          kindErrors.toList.mkString("\n", ", ", ""))
      } 

So now the point is understanding why checkKindBounds(tparams, targs, pre, owner) returns those errors. If you go down the method call chain, you will see that the checkKindBounds call another method

val errors = checkKindBounds0(tparams, targs, pre, owner, true)

You'll see the problem is connected to checking bounds of higher-kinded type, at line 5784, inside checkKindBoundsHK :

 if (!sameLength(hkargs, hkparams)) {
        if (arg == AnyClass || arg == NothingClass) (Nil, Nil, Nil) // Any and Nothing are kind-overloaded
        else {error = true; (List((arg, param)), Nil, Nil) } // shortcut: always set error, whether explainTypesOrNot
      }

The test is not passed, it appears that in my debugger:

hkargs$1 = {scala.collection.immutable.Nil$@2541}"List()"
arg$1 = {scala.tools.nsc.symtab.Symbols$ClassSymbol@2689}"class List"
param$1 = {scala.tools.nsc.symtab.Symbols$TypeSymbol@2557}"type B"
paramowner$1 = {scala.tools.nsc.symtab.Symbols$MethodSymbol@2692}"method process"
underHKParams$1 = {scala.collection.immutable.$colon$colon@2688}"List(type R)"
withHKArgs$1 = {scala.collection.immutable.Nil$@2541}"List()"
exceptionResult12 = null
hkparams$1 = {scala.collection.immutable.$colon$colon@2688}"List(type R)"

So it appears like there is one higher kinded param, type R, but there is no provided value for that.

If you actually go back to the to checkKindBounds, you see that after the snippet:

 val (arityMismatches, varianceMismatches, stricterBounds) = (
        // NOTE: *not* targ.typeSymbol, which normalizes
        checkKindBoundsHK(tparamsHO, targ.typeSymbolDirect, tparam, tparam.owner, tparam.typeParams, tparamsHO)
      )

the arityMismatches contains a tuple List, B. And now you can also see that the error message is wrong:

inferred kinds of the type arguments (MyFoo,MyFoo,List[X]) do not conform to the expected kinds of the type parameters (type F,type R,type B). List[X]'s type parameters do not match type B's expected parameters: class List has one type parameter, but type B has ZERO

In fact if you put a breakpoint at line 5859 on the following call

checkKindBoundsHK(tparamsHO, targ.typeSymbolDirect, tparam, tparam.owner, tparam.typeParams, tparamsHO)

you can see that

tparam = {scala.tools.nsc.symtab.Symbols$TypeSymbol@2472}"type B"
targ = {scala.tools.nsc.symtab.Types$UniqueTypeRef@2473}"List[X]"

Conclusion:

For some reason, when dealing with complex higher-kinded types such as yours, Scala compiler inference is limited. I don't know where it does come from, maybe you want to send a bug to the compiler team

like image 177
Edmondo1984 Avatar answered Oct 06 '22 01:10

Edmondo1984