Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Local assignment affects type?

Tags:

generics

scala

In the following example, f3 can take a Iterable[Array[Int]]

  def f3(o:Iterable[Iterable[Any]]):Unit = {}

  f3(Iterable(Array(123)))    // OK. Takes Iterable[Array[Int]]

but if I assign the Iterable[Array[Int]] to a local variable, it cannot:

  val v3 = Iterable(Array(123))
  f3(v3)    // Fails to take Takes Iterable[Array[Int]]

with the error:

  Error:(918, 10) type mismatch;
  found   : Iterable[Array[Int]]
  required: Iterable[Iterable[Any]]
  f3(x)

What the fudge? Why does the first example work, but not the seconds. It seems to have something to do with nested generics:

  def f(o:Iterable[Any]):Unit = {}
  f( Array(123))
  val v1 = Array(123)
  f(v1)  // OK

  def f2(o:Iterable[Any]):Unit = {}
  f2( Iterable(Array(123)))
  val v2 = Array(123)
  f(v2) // OK

With scala.2.11

like image 505
user48956 Avatar asked Jun 20 '16 19:06

user48956


People also ask

How do I fix local variable might be referenced before assignment?

The UnboundLocalError: local variable referenced before assignment error is raised when you try to assign a value to a local variable before it has been declared. You can solve this error by ensuring that a local variable is declared before you assign it a value.

How do I fix local variables before assignment in Python?

The Python "UnboundLocalError: Local variable referenced before assignment" occurs when we reference a local variable before assigning a value to it in a function. To solve the error, mark the variable as global in the function definition, e.g. global my_var .

What is the assignment variable?

Variable Assignment To "assign" a variable means to symbolically associate a specific piece of information with a name. Any operations that are applied to this "name" (or variable) must hold true for any possible values.

What is assignment expression?

Assignment expressions allow you to assign and return a value in the same expression. For example, if you want to assign to a variable and print its value, then you typically do something like this: >>> >>> walrus = False >>> print(walrus) False.


2 Answers

First of all, it's important that Array doesn't extend Iterable (because it's a Java type). Instead there is an implicit conversion from Array[A] to Iterable[A], so expected types matter.

In the first case: Iterable(Array(123)) is an argument to f3 and thus is typechecked with expected type Iterable[Iterable[Any]]. So Array(123) is typechecked with expected type Iterable[Any]. Well, its actual type is Array[Int] and the compiler inserts the conversion (because Iterable[Int] conforms to Iterable[Any]). So this is actually Iterable(array2iterable(Array(123)) (I don't remember the exact name).

In the second case f3 has the type Iterable[Array[Int]]: there is nothing to trigger the implicit conversion in the val f3 = ... line, right? And there is no implicit conversion from Iterable[Array[Int]] to Iterable[Iterable[Int]] (or, more generally from Iterable[A] to Iterable[B] when there is an implicit conversion from A to B), so the next line fails to compile. You could write this conversion yourself, but it wouldn't help e.g. to convert Array[Array[Int]] to Iterable[Iterable[Int]].

And of course, if you use Iterable[Any], there is again nothing to trigger the implicit conversion!

like image 173
Alexey Romanov Avatar answered Sep 28 '22 00:09

Alexey Romanov


This has to do with the way type inference/unification works in Scala.

When you define a variable and leave out the type, Scala applies the most specific type possible:

scala> val v1 = Iterable(Array(123))
v1: Iterable[Array[Int]] = List(Array(123))

However, when you specify the expected type (eg by passing the value to a function with a defined parameter type) Scala unifies the given parameter with the expected type (if possible) :

scala> val v2 : Iterable[Iterable[Any]] = Iterable(Array(123))
v2: Iterable[Iterable[Any]] = List(WrappedArray(123))

Since Int is a subtype of Any, unification occurs and the code runs just fine.

If you want your function to accept anything that is a subtype of Any (without Scala's unification help) you will have to define this behavior explicitly.

Edit:

While what I am saying is partially true, see @AlexyRomanov's answer for a more correct assessment. It seems that the "unification" between Array and Iterable is really an implicit conversion being called when you pass Iterable(Array(123)) as a parameter (see the effect of this in my declaration of v2).

Suppose you have a bit of code where the compiler expects type B but finds type A instead. Before throwing an error, the compiler checks a collection of implicit conversion functions for one with the type A => B. If the compiler finds a satisfactory conversion, the conversion is applied automatically (and silently).

The reason f3 doesn't like v1 is because it is too late to call an implicit conversion on the inner Array[Int] and no existing implicit conversion exists for Iterable[Array[Int]] => Iterable[Iterable[Int]], though it would be trivial to implement, as I show below:

scala> implicit def ItAr2ItIt[T](ItAr: Iterable[Array[T]]): Iterable[Iterable[T]] = ItAr.map(_.toIterable)
ItAr2ItIt: [T](ItAr: Iterable[Array[T]])Iterable[Iterable[T]]

scala> def f3(o:Iterable[Iterable[Any]]):Unit = println("I like what I see!")
f3: (o: Iterable[Iterable[Any]])Unit

scala> val v3 = Iterable(Array(123))
v3: Iterable[Array[Int]] = List(Array(123))

scala> f3(v3)
I like what I see!
like image 24
evan.oman Avatar answered Sep 28 '22 00:09

evan.oman