Showing posts with label invariance. Show all posts
Showing posts with label invariance. Show all posts
Wednesday, March 31, 2010
Variant Positions 2
This is a continuation of post: Variant Positions 1
...
My first attempt at the Verified was to make it a mutable object (my Java tourettes kicking in). But it cannot be covariant and mutable. Look at the code to see why:
For Verified to be mutable A must be invariant. If you look at the Mutable collections in Scala they are all invariant.
Here is an interesting example of both invariant and covariant type parameters in a class hierarchy:
This example is perfectly legal because no matter how X[Any] is used no illegal assignment in Y can occur. The interesting thing is that the object can be used in covariant usecases when only X is required. This is now the collections in Scala can work.
Here is a little example of collections invariance and covariance in action. In List the parameter is covariant but in Buffer it is invariant
...
My first attempt at the Verified was to make it a mutable object (my Java tourettes kicking in). But it cannot be covariant and mutable. Look at the code to see why:
- class Verified[+A <: V,V](assertion : (V) => Boolean, private var value : A){
- assert(assertion(value))
-
- def a = value
- // update is illegal. See the example below
- def update[ B >: A <: V](a : B) = value = a
- }
- def notNull(obj : AnyRef) = obj != null
- val v = new Verified(notNull, "hi")
- /*
- Up to this point everything looks ok but the next line
- will assign an object to value which is a reference to a String
- */
- v update (new Object())
For Verified to be mutable A must be invariant. If you look at the Mutable collections in Scala they are all invariant.
Here is an interesting example of both invariant and covariant type parameters in a class hierarchy:
- scala> class X[+A](val x :A)
- defined class X
- scala> class Y[A](var a: A) extends X[A](a)
- defined class Y
- scala> val x: X[Any] = new Y[String]("hi")
- x: X[Any] = Y@1732a4df
- scala> x.asInstanceOf[Y[String]].a="ho"
This example is perfectly legal because no matter how X[Any] is used no illegal assignment in Y can occur. The interesting thing is that the object can be used in covariant usecases when only X is required. This is now the collections in Scala can work.
Here is a little example of collections invariance and covariance in action. In List the parameter is covariant but in Buffer it is invariant
- scala> def printList(l : List[Any]) = print(l mkString " :: ")
- printList: (l: List[Any])Unit
- scala> val buffer = Buffer(1,2,3)
- buffer: scala.collection.mutable.Buffer[Int] = ArrayBuffer(1, 2, 3)
- scala> printList(buffer)
- 1 :: 2 :: 3
- /*
- ++ is part of Iterable. Since Iterable is covariant ++
- returns a new buffer it does not modify the existing buffer
- All mutators are only defined on invariant traits
- */
- scala> buffer ++ List(4)
- res16: scala.collection.mutable.Buffer[Int] = ArrayBuffer(1, 2, 3, 4)
- scala> res16 eq buffer
- res17: Boolean = false
- /*
- buffer defines += to add an element to the buffer
- so res27 is the same buffer with an extra element
- */
- scala> buffer += 10
- res27: buffer.type = ArrayBuffer(1, 2, 3, 10)
- scala> res27 eq buffer
- res28: Boolean = true
Labels:
collections,
covariance,
intermediate,
invariance,
Scala,
variance
Tuesday, March 30, 2010
Variant Positions 1
An additional topic on variance to finish up the major points on the topic. The previous two posts: contain required information for following this post.
In-, co- and contra- variance are the three types of variance expressible in Scala. I showed how this affects assignments and arguments being pass to methods in the last two topics. This looks at how the different types of variance influences how classes can be defined. In the last post we saw how the compiler complained about a method definition in a covariant class because the compiler recognized that such a definition was inherently dangerous and must be prohibited. The example was:
For an class like Output it does not make sense to have A be covariant so we changed A to be contravariant. However suppose we have a collection type class.
The previous definition is not legal because value and a in the parameter of a_= "occur in a contravariant position." What to do? Making A contravariant isn't an option:
In-, co- and contra- variance are the three types of variance expressible in Scala. I showed how this affects assignments and arguments being pass to methods in the last two topics. This looks at how the different types of variance influences how classes can be defined. In the last post we saw how the compiler complained about a method definition in a covariant class because the compiler recognized that such a definition was inherently dangerous and must be prohibited. The example was:
- scala> class Output[+A] {def write(a : A) = () /*do write*/ }
- < console>:5: error: covariant type A occurs in contravariant position in type A of value a
- class Output[+A] {def write(a : A) = () /*do write*/ }
- ^
For an class like Output it does not make sense to have A be covariant so we changed A to be contravariant. However suppose we have a collection type class.
- class Verified[+A] (assertion : (A) => Boolean, value : A){
- assert(assertion(value))
-
- def a = value
- def a_=(a : A) = new Verified(assertion, a)
- }
The previous definition is not legal because value and a in the parameter of a_= "occur in a contravariant position." What to do? Making A contravariant isn't an option:
- class Verified[+A <: V,V](assertion : (V) => Boolean, val value : A){
- assert(assertion(value))
- /*
- this is the key. Restrict possible types of
- A Since B is a super (or equal) type of A
- */
- def update[ B >: A <: V](a : B) = new Verified(assertion, a)
- }
- // example useage
- scala> def notNull(obj : AnyRef) = obj != null
- notNull: (obj: AnyRef)Boolean
- scala> val v = new Verified(notNull, "hi")
- v: Verified[java.lang.String,AnyRef] = Verified@307b37df
- scala> val newV = v update (new Object())
- newV: Verified[java.lang.Object,AnyRef] = Verified@36f72f09
- // 3 is not legal because the type parameter 'V' is AnyRef. Int is a subclass of Any NOT AnyRef
- scala> val newV = v update (3)
- < console>:8: error: inferred type arguments [Any] do not conform to method update's type parameter bounds [B >: java.lang.String <: AnyRef]
- val newV = v update (3)
- ^
Labels:
contravariance,
covariance,
intermediate,
invariance,
Scala,
variance
Wednesday, March 24, 2010
In- and Co- variance of type parameters
In Java most parameterized types are considered to be "invariant". What does that mean? Here is an example to explain:
Assignment compatibility has multiple dimensions: the object type and the types of the parameters. Unlike object type the compatibility of the type-parameters can be covariant, contravariant and invariant. Java has invariant parameters and that is demonstrated by the previous example. Covariant parameters allow subclassing. Contravariant parameters need their own topic.
- /*
- This is an example of a parameterized class that with an invariant parameter B
- In both Scala and Java parameters are invariant by default.
- */
- scala> class Invariant[B]
- defined class Invariant
- scala> var x : Invariant[Object] = new Invariant[Object]
- x: Invariant[java.lang.Object] = Invariant@2e0c5575
- /*
- Note: Invariant[String] cannot be assigned to Invariant[Object]
- even though logically it seems like it should be.
- This is the effect of invariance. Covariant parameters do not have
- this restriction.
- */
- scala> var x : Invariant[Object] = new Invariant[String]
- < console>:6: error: type mismatch;
- found : Invariant[String]
- required: Invariant[java.lang.Object]
- var x : Invariant[Object] = new Invariant[String]
- ^
- scala> class Sub[A] extends Invariant[A]
- defined class Sub
- /*
- Since Sub is a subclass of Invariant it can be assigned
- (but not Sub[String])
- */
- scala> val x : Invariant[Object] = new Sub[Object]
- x: Invariant[java.lang.Object] = Sub@26ced1a8
Assignment compatibility has multiple dimensions: the object type and the types of the parameters. Unlike object type the compatibility of the type-parameters can be covariant, contravariant and invariant. Java has invariant parameters and that is demonstrated by the previous example. Covariant parameters allow subclassing. Contravariant parameters need their own topic.
- // The '+' indicates the parameter is covariant
- scala> class Covariant[+B]
- defined class Covariant
- scala> var x : Covariant[Object] = new Covariant[Object]
- x: Covariant[java.lang.Object] = Covariant@315cb235
- // Now this is legal
- scala> var x : Covariant[Object] = new Covariant[String]
- x: Covariant[java.lang.Object] = Covariant@26e2e276
- /*
- Warning: The following is not legal because
- you cannot supply an invariant parameter
- with a covariant value.
- */
- scala> class Sub[+A] extends Invariant[A]
- < console>:7: error: covariant type A occurs in invariant position in type [+A]Invariant[A] with ScalaObject{def this(): Sub[A]} of class Sub
- class Sub[+A] extends Invariant[A]
- ^
- scala> class Sub[+A] extends Covariant[A]
- defined class Sub
- scala> class Sub[A] extends Covariant[A]
- defined class Sub
Labels:
covariance,
invariance,
Scala,
variance
Subscribe to:
Comments (Atom)