2

I am using Scala 2.10-RC5

Here is my code:

object Fbound {
    abstract class E[A <: E[A]] {
        self: A =>
        def move(a: A): Int
    }
    class A extends E[A] {
        override def toString = "A"
        def move(a: A) = 1
    }
    class B extends E[B] {
        override def toString = "B"
        def move(b: B) = 2
    }
    def main(args: Array[String]): Unit = {
        val a = new A
        val b = new B
        val l = List(a, b)
        val t = l.map(item => item.move(null.asInstanceOf[Nothing]))
        println(t)
    }
}

when run the program, exception occurs:

Exception in thread "main" java.lang.NullPointerException
    at fb.Fbound$$anonfun$1.apply(Fbound.scala:20)
    at fb.Fbound$$anonfun$1.apply(Fbound.scala:20)
    at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:244)
    at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:244)
    at scala.collection.immutable.List.foreach(List.scala:309)
    at scala.collection.TraversableLike$class.map(TraversableLike.scala:244)
    at scala.collection.AbstractTraversable.map(Traversable.scala:105)
    at fb.Fbound$.main(Fbound.scala:20)
    at fb.Fbound.main(Fbound.scala)

my question is:

  1. Why it pass the compiler but fail at runtime?
  2. Adding a line at the bottom - val t1 = l.map(item => item.move(item)) will fail the compiler, why?
Arpit
  • 6,212
  • 8
  • 38
  • 69
chenhry
  • 496
  • 4
  • 7
  • hi. it is very confusing that you use A both as a type parameter and as a class name. i'd try rewriring your example with classes called "First" and "Second" and see if things aren't clearer. if you think this would break something, if you are expecting the classname A to have any relationship to the A <: E[A] in your type bound, then that is something you should rethink. (i hope that the A in "self: A =>" constraint would be resolved to the type parameter not the classname, but again, the dual use of A and B here makes things very confusing.) – Steve Waldman Dec 28 '12 at 09:59
  • 6
    1. It's impossible to get instance of `Nothing`. `null.asInstanceOf[Nothing]` throws `java.lang.NullPointerException`. – senia Dec 28 '12 at 10:02
  • just use `null` instead of `null.asInstanceOf[Nothing]` and everything will work fine. – tenshi Dec 28 '12 at 11:20
  • 1
    The question the author is asking is not related to the result of his code, but rather to the reasoning behind the type checking made by the compiler. The issue here is not why `null.asInstanceOf[Nothing]` (obviously) doesn't work, it is why the compiler fails when he writes `l.map(item => item.move(item))` and not when he passes a `Nothing` argument (that last one is understandable). – Rui Gonçalves Dec 30 '12 at 19:39

2 Answers2

3

Your code with null.asInstanceOf[Nothing] compiles because Nothing is a subclass of everything and, as such, complies with the required type for move. Needless to say, it will throw an exception at runtime.

When you try to compile the second line you gave, you are given something in the lines of this error:

<console>:19: error: type mismatch;
 found   : E[_6(in value $anonfun)] where type _6(in value $anonfun) >: B with A
 <: E[_ >: B with A <: Object]
 required: <root>._6

When you put the two instances in the same List, you have lost important information about the type of their elements. The compiler can't ensure that an element of type T >: B with A <: E[_ >: B with A] can be passed to the move method of an object of the same type, the same way that you can't do:

val c: E[_ >: B with A] = new A
val d: E[_ >: B with A] = new B
c.move(d) // note: the _ in c and d declarations are different types!

I don't know enough about self-types to be completely sure of this explanation, but it seems to me that it is a class-level restriction, and not an instance-level one. In other words, if you lose the information about the type parameter in E, you can't expect the compiler to know about the move argument particular type.

For instance-level restrictions, you have this.type. If you define move as:

def move(a: this.type): Int

Your code compiles, but I don't think it is what you want, as move will accept only the same instance you are calling it on, which is useless.

I can't think of any way you may enforce that restriction the way you want. I suggest you try to do that with type variables (i.e. defining a type variable type T = A in class E), which have, as far as I know, some degree of binding to instances. Perhaps you can explain in more detail your specific situation?

Rui Gonçalves
  • 1,355
  • 12
  • 28
  • 1
    Rui, thank you very much. I wrote the code for better understanding of F-Bounded Polymorphism which, for me, looks weird in its self-recursive appearance - E[A <: E[A]]. – chenhry Dec 31 '12 at 08:07
  • other related question: if I replace the key word 'class' with 'trait', ie. I redefine A and B as trait and leave other unchanged, I coudn't find a way to instantialize an instance of 'A with B', ie. val ab = new A with B. I am really confused about the error message the compile gives. – chenhry Dec 31 '12 at 08:17
  • The practical difference between traits and abstract classes in Scala is that you can inherit from more than one trait, but only from one class (abstract or not). If you picture the relations between your classes as an inheritance tree, you can see that `A with B` has both `E[A]` and `E[B]` as ancestors, which is illegal. – Rui Gonçalves Dec 31 '12 at 14:03
2

Re: second question

The problem is type of the argument of move method, I can't see it being possible to make it work if it is in any way tied to subclass(es), instead:

  • l has to be 'some form' of list of E-s
  • so then item will be 'some form' of E
  • type of argument to move has to have the same type

This is then the only way I could make it work with type arguments (changing btw name of type argument to X, not to be confused with name of class A, and removing self type declaration, which I think is irrelevant for this problem/discussion):

abstract class E[X <: E[X]]
{
    def move (a: E[_]): Int
}

class A extends E[A]
{
    def move(a: E[_]): Int = 1
}
class B extends E[B]
{
    def move(b: E[_]): Int = 2
}

...
{
    val a = new A
    val b = new B
    val l = List (a, b)
    val t = l.map (item => item.move(item))
    ...
}

If someone could give a more 'type-restrictive' solution, I would really like to see it.


Another option, as Rui has suggested, would be to use a type member, something like this:

abstract class E
{
    type C <: E
    def move (x: E): Int
}

class A extends E
{
    type C = A
    def move(x: E): Int = 1
}

class B extends E
{
    type C = B
    def move(x: E): Int = 2
}

...
{
    val a = new A
    val b = new B
    val l = List (a, b)
    val t = l.map (item => item.move(item))
    ...
}
Duduk
  • 166
  • 5