Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is passing Int where an F[_] parameter is expected valid?

Let's say we have a function:

def bar[F[_], A](x: F[A], y: F[A]) = null

All the following behavior are clear:

bar(List(1, 2, 3), List(1)) // compiles ok
bar(List(1), Some(1)) // doesn't compile

But,

bar(1, List(1)) // compiles ok
bar(1, 1) // compiles ok

Why?

P.S. example from FSiS Part 1 - Type Constructors, Functors, and Kind Projector

like image 625
Artsiom Miklushou Avatar asked Sep 30 '15 21:09

Artsiom Miklushou


2 Answers

I think the following gives a clue (although there is still some mystery involved):

def baz[F[_], A](x: F[A]): F[A] = x

scala> baz("str")
res5: Comparable[String] = str

scala> baz(1)
res6: Any = 1

scala> class Foo
defined class Foo

scala> baz(new Foo)
<console>:15: error: no type parameters for method baz: (x: F[A])F[A] exist so that it can be applied to arguments (Foo)
 --- because ---
argument expression's type is not compatible with formal parameter type;
 found   : Foo
 required: ?F
              baz(new Foo)
              ^
<console>:15: error: type mismatch;
 found   : Foo
 required: F[A]
              baz(new Foo)
                  ^

scala> case class Foo2
warning: there were 1 deprecation warning(s); re-run with -deprecation for details
defined class Foo2

scala> baz(Foo2)
res10: scala.runtime.AbstractFunction0[Foo2] = Foo2

So the functions bar and baz are finding any container type they can (Comparable for String, AbstractFunction0 for a case class, etc) to match the expected F[_].

Speculation: in the case of Int, I suspect we are hitting a special "container" type for what are (boxed) primitive types in the underlying bytecode. If this special type can only be printed back to Scala as "Any", but is really some special type we can think of as "Any[_]", then that could explain the results we see. As corroboration that this is to do with the special handling of primitives, note that it fails for non-primitive simple types, like the (non-case) class Foo above.

like image 55
Shadowlands Avatar answered Sep 19 '22 01:09

Shadowlands


I think you're running into a limitation of the type inference system. To shed some light on this, let's look at what happens when we redefine this a bit to get a more useful output:

class Bar[F[_], A](x: F[A], y: F[A]) {}
res0: Bar[List,Int] = Bar@69f1a286
new Bar(List(1,2,3), List(1))
res1: Bar[Any,Int] = Bar@7b139eab
new Bar(List(1), 1)
res2: Bar[Any,Int] = Bar@456be73c
new Bar(List(1), Some(1))
<console>:12: error: inferred kinds of the type arguments (Product with java.io.Serializable,Int) do not conform to the expected kinds of the type parameters (type F,type A) in class Bar.
Product with java.io.Serializable's type parameters do not match type F's expected parameters:
<refinement of Product with java.io.Serializable> has no type parameters, but type F has one
       new Bar(List(1), Some(1))
       ^
<console>:12: error: type mismatch;
 found   : List[Int]
 required: F[A]
       new Bar(List(1), Some(1))
                   ^
<console>:12: error: type mismatch;
 found   : Some[Int]
 required: F[A]
       new Bar(List(1), Some(1))

In the first example, we have a Bar[List, Int], which makes perfect sense, we passed in two List[Int].

In the second and third, we have a Bar[Any, Int]. Here's where it gets weird. Keep in mind that Any is the parent of both AnyVal (the parent of Scala's equivalents of Java's primitives) and AnyRef (the Scala equivalent of Java's Object) (see Scala Documentation for further explanation).

Scala's type inference has decided that this Bar's constructor should accept Any for F, and Int for A. As Any is indeed a parent of List and Int, that's fine. The List is indeed parameterized as [Int], so that's fine. What's weird is that Scala is okay with saying that the Int is also of type Any[Int]. I don't have a good explanation for that part.

With the last one, that's where I'm honestly confused, and I have to wonder if this is a bug. For some reason, even though both List and Some are children of Any, and both are parameterized with Int, it doesn't allow it. I'm afraid I'm not well versed in the intricacies of the compiler's inference methods, but for what it's worth, explicitly specify those parameters works:

new Bar[Any,Int](List(1), Some(1))
res14: Bar[Any,Int] = Bar@36238b12

To me, that suggests the type inference system just can't properly infer the types, or it's inferring types that aren't correct.

like image 39
childofsoong Avatar answered Sep 21 '22 01:09

childofsoong