Contravariance Clarification...

45 views
Skip to first unread message

Daniel Hinojosa

unread,
Apr 11, 2011, 1:26:39 PM4/11/11
to scala...@googlegroups.com
I am not trying to make this work, I am trying to understand it.

    class Fruit
    class Citrus extends Fruit
    class Orange extends Citrus
    class Tangelo extends Citrus
    class Apple extends Fruit
    class Banana extends Fruit

    class MyContainer[-A](a: A) {
      private[this] var item = a

      def set(a: A) {
        item = a
      }
      //No get
      override def toString = item.toString
    }

    val fruitbasket = new MyContainer(new Orange)
    fruitbasket.set(new Banana) //ERROR
    val boxOfBananas = fruitbasket.asInstanceOf[MyContainer[Banana]]
    boxOfBananas.set(new Banana)

Just a few items I need clarification on, why the error on fruitbasket.set since all I have to do is cast it and set it that way? Is it just being strict type in this case?

Also I am assuming that
       
    val boxOfString = fruitBasket.asInstanceOf[MyContainer[String]]
    boxOfString.set("Bob")



works because String is an AnyRef and since I am contravariant that means I can essentially put anything in there.
 

Paul Phillips

unread,
Apr 11, 2011, 1:36:50 PM4/11/11
to scala...@googlegroups.com, dh.evolu...@gmail.com
On 4/11/11 10:26 AM, Daniel Hinojosa wrote:
> val fruitbasket = new MyContainer(new Orange)
> fruitbasket.set(new Banana) //ERROR
> val boxOfBananas = fruitbasket.asInstanceOf[MyContainer[Banana]]
> boxOfBananas.set(new Banana)
>
> Just a few items I need clarification on, why the error on
> fruitbasket.set since all I have to do is cast it and set it that way?

That's only "all you have to do" in the sense that you can make anything
compile by casting it. Making it compile is not the goal, making it
work is.

> Is it just being strict type in this case?

It's telling you that this is unsound.

> Also I am assuming that
>
> val boxOfString = fruitBasket.asInstanceOf[MyContainer[String]]
> boxOfString.set("Bob")
>
> works because String is an AnyRef and since I am contravariant that
> means I can essentially put anything in there.

No, it "works" because you cast it. Those two lines would work equally
well if MyContainer were invariant or covariant. The actual storage
cell in MyContainer can hold anything, and once you abandon type safety
with the casts it won't stop you from putting anything there. You'll
discover why this is a bad idea as soon as you try to get an orange out
of the basket and find bob lounging in there instead.

HamsterofDeath

unread,
Apr 11, 2011, 1:37:04 PM4/11/11
to scala...@googlegroups.com
Am 11.04.2011 19:26, schrieb Daniel Hinojosa:
val fruitbasket = new MyContainer(new Orange)
this gets desugared to
val fruitbasket:MyContainer[Orange] = new MyContainer(new Orange)

and since oranges are not bananas, you cannot do

fruitbasket.set(new Banana) //ERROR


co/contravariance has no effect in your example.

Sciss

unread,
Apr 11, 2011, 1:38:29 PM4/11/11
to scala...@googlegroups.com
If i'm not mistaken, your casting "works" because the type error would occur in generics (a type parameter of Fruit), which are currently erased after compilation, so to the JVM it is as if you call a method

public void set( a: java.lang.Object ) { ... }

thus there is no ClassCastException when setting the banana. It would occur once you try to call a method on the item that would require it to be an Orange.

best, -sciss-

Daniel Hinojosa

unread,
Apr 11, 2011, 1:49:10 PM4/11/11
to scala...@googlegroups.com, dh.evolu...@gmail.com
Alright, so casting is "rules are off" on type safety.
 

Daniel Hinojosa

unread,
Apr 11, 2011, 1:56:53 PM4/11/11
to scala...@googlegroups.com
I was just surprised at the ease with casting, seems that in Java I was stopped at the least with a runtime exception of CastClassException.  Maybe I am wrong. 

Daniel Hinojosa

unread,
Apr 11, 2011, 2:00:46 PM4/11/11
to scala...@googlegroups.com
Oh that's Right, right! So, it begs the next question: Couldn't a Manifest[A] runtime check that for even more type safety? or would casting still be able to work around that?

Razvan Cojocaru

unread,
Apr 11, 2011, 5:23:25 PM4/11/11
to scala...@googlegroups.com

The way co-variance works is that , if you had an M[+A], then M[Orange] and M[Banana] would both be a subtype of M[Fruit]. You can’t stuff a Banana in an M[Orange] but you can stuff a Banana in an M[Fruit].

 

The way counter-variance works is that MyContainer[Fruit] is a subtype of MyContainer[Orange] AND also a subtype of MyContainer[Banana]…. So there is no common type of the container between MyContainer[Orange] and MyContainer[Banana]… you can’t stuff Bananas in an Orange container… but you can still stuff a Banana in a MyContainer[Fruit]... cause’ it’s a Fruit… but you still end up with a MyContainer[Fruit].

 

Your example doesn’t really have any bearing on contra or co-variance. It wouldn’t work if you did [+A] either… because the container type was inferred to be [Orange], when instantiated and won’t change.

 

Why the compiler won’t let you do M[+A] is because of the set method

 

scala> class M[+A] { def set (a:A) {} }

<console>:6: error: covariant type A occurs in contravariant position in type A of value a

       class M[+A] { def set (a:A) {} }

 

BUT – you can’t stuff Bananas in a container of Oranges, not matter what you do…

 

…short of hacking it that is, which is what you did J with the casting.

Stefan Langer

unread,
Apr 12, 2011, 5:23:24 AM4/12/11
to Razvan Cojocaru, scala...@googlegroups.com
Actually the example wouldn't work with M[+A] as A is used in a method
parameter.
The compiler would not let this through as it is unsound.

-Stefan

2011/4/11 Razvan Cojocaru <p...@razie.com>:

Tony Morris

unread,
Apr 12, 2011, 5:40:46 AM4/12/11
to scala...@googlegroups.com
On 12/04/11 03:49, Daniel Hinojosa wrote:
> Alright, so casting is "rules are off" on type safety.
>
>
Yes. We can prove this easily, even with a truth table.
def cast[A, B]: (a: A) => B = _.asInstanceOf[B]

Under C-H Isomorphism, this corresponds to:
forall A B. A -> B

Let's write the truth table:
A B A->B
0 0 1
0 1 1
1 0 0 <-- logical inconsistency!
1 1 1

The rules are off (QED). It is a "lie", or "logically inconsistent."

As for contravariance and covariance, these correspond to contramap and
map respectively (for some functor F):

def contramap[A, B](f: A => B, b: F[B]): F[A]
def map[A, B](f: A => B, a: F[A]): F[B]

In the case of scala's variance annotations these correspond to using
the inheritance relationship for the first argument of the two
aforementioned methods.

That is to say, for contravariance, if you have a way to go from A => B
(by inheritance), then you have a way to go from F[B] to F[A] (by
inheritance). Similarly for map.


--
Tony Morris
http://tmorris.net/


Daniel Hinojosa

unread,
Apr 12, 2011, 7:24:12 PM4/12/11
to scala...@googlegroups.com, tmo...@tmorris.net
Man, you just blow my mind,

1/2 the time I don't know what you are talking about, but that's good.  It is my lot this year to play catch up to you and a couple other uber-functional devs.  First stop, C-H Isomorphism.

Dean Wampler

unread,
Apr 12, 2011, 9:22:04 PM4/12/11
to scala...@googlegroups.com, Daniel Hinojosa
Just to clarify a few points that others have mentioned, all the -A lets you do is declare a variable of type of MyContainer[Orange], for example, and then assign to that variable a MyContainer[Citrus] or MyContainer[Fruit]. for example:

val basket: MyContainer[Orange] = new MyContainer[Citrus](new Orange)

This works because the -A means that MyContainer[Citrus] is a subtype of (i.e., is substitutable for) MyContainer[Orange].  Oddly enough, even though the "real" type is MyContainer[Citrus], the set method will now only accept Oranges or subclasses thereof. That is, the type that set accepts is fixed to be "A" (Orange) or a subtype of A.

--
Dean Wampler
co-producer, Development Languages, Practices, and Techniques stage at Agile2011
"Programming Scala" (O'Reilly)  http://programmingscala.com
twitter: @deanwampler, @chicagoscala
http://polyglotprogramming.com

Reply all
Reply to author
Forward
0 new messages