This page is no longer maintained — Please continue to the home page at www.scala-lang.org

Doubt regarding Lists

26 replies
nik_m
Joined: 2009-06-13,
User offline. Last seen 42 years 45 weeks ago.

Hi, I am new to Scala and just started learning it.

I read that List are immuatbale sequence of objects of SAME type. But I
tried writing
a List containing differnt types and it compiles successfully.

val one = List(1,2,"3") // Is a List of both Int and String

Can anyone please explain this behavior ?

msongy
Joined: 2009-05-25,
User offline. Last seen 39 weeks 14 hours ago.
Re: Doubt regarding Lists

If you enter that into the REPL you'll see that the inferred type is
List[Any]:

scala> val one = List(1,2,"3")
one: List[Any] = List(1, 2, 3)

Any is a common supertype of both Int and String. What you read about
List is correct, but they are immutable sequences of objects of the same
static types, not necessarily of the same runtime types. If you change
the 3rd list element to be another integer, the type will be inferred to
be List[Int], as expected:

scala> val two = List(1, 2, 3)
two: List[Int] = List(1, 2, 3)

nik_m wrote:
> Hi, I am new to Scala and just started learning it.
>
> I read that List are immuatbale sequence of objects of SAME type. But I
> tried writing
> a List containing differnt types and it compiles successfully.
>
> val one = List(1,2,"3") // Is a List of both Int and String
>
> Can anyone please explain this behavior ?
>

Sean McCauliff
Joined: 2009-06-13,
User offline. Last seen 42 years 45 weeks ago.
Re: Doubt regarding Lists

I'm also new to Scala, so this is just my guess.

'one' is of type List[Any], just like in Java you can have List.

Sean

On Sat, Jun 13, 2009 at 8:58 AM, nik_m wrote:
>
> Hi, I am new to Scala and just started learning it.
>
> I read that List are immuatbale sequence of objects of SAME type. But I
> tried writing
> a List containing differnt types and it compiles successfully.
>
> val one = List(1,2,"3") // Is a List of both Int and String
>
> Can anyone please explain this behavior ?
> --
> View this message in context: http://www.nabble.com/Doubt-regarding-Lists-tp24013720p24013720.html
> Sent from the Scala - User mailing list archive at Nabble.com.
>
>

Mohamed Bana
Joined: 2008-12-20,
User offline. Last seen 3 years 19 weeks ago.
Re: Doubt regarding Lists

i think it's inferring the most general type to make it type check.
maybe if you pass a type parameter

scala> val one = List[Int](1,2,"3")
:4: error: type mismatch;
found : java.lang.String("3")
required: Int
val one = List[Int](1,2,"3")

nik_m wrote:
> Hi, I am new to Scala and just started learning it.
>
> I read that List are immuatbale sequence of objects of SAME type. But I
> tried writing
> a List containing differnt types and it compiles successfully.
>
> val one = List(1,2,"3") // Is a List of both Int and String
>
> Can anyone please explain this behavior ?

David Pollak
Joined: 2008-12-16,
User offline. Last seen 42 years 45 weeks ago.
Re: Doubt regarding Lists
This relates to variance.  Lists are covariant.  Here's the section from Beginning Scala on Variance:

Variance

Variance is an important and challenging concept. It defines the rules by which parameterized types can be passed as parameters. In the beginning of the chapter, we showed how passing a String[] (Java notation) to a method expecting an Object[] can cause problems. Java allows you to pass an array of something to a method expecting an array of something’s superclass. This is called covariance. On the surface, this makes a lot of sense. If you can pass a String to a method expecting an Object, why can’t you pass an Array[String] (Scala notation) to a method expecting an Array[Object]? Because Array is mutable: it can be written to in addition to being read from, so a method that takes an Array[Object] may modify the Array by inserting something that cannot be inserted into an Array[String]. Defining the type variance for type parameters allows you to control how parameterized types can be passed to methods.

Variance comes in three flavors: invariant, covariant, and contravariant. Type parameters can be individually marked as covariant or contravariant and are by default invariant.

Invariant Parameter Types

In Scala, Array[T] is invariant. This means that you can only pass an Array[String] to foo(a: Array[String]) and that you can only pass an Array[Object] to bar(a: Array[Object]). This ensures that what is read from or written to the array is something of the correct type. So, for anything that’s mutable, the type parameter should be invariant. You do this by doing nothing with the type parameter. So, let’s define an invariant class:

class Holder[T](var data: T)

The class holds data of type T. Let’s write a method:

scala> def add(in: Holder[Int]) {in.data = in.data + 1}

add: (Holder[Int])Unit

scala> val h = new Holder(0)

h: Holder[Int] = Holder@bc0eba

scala> add(h)


scala> h.data

res2: Int = 1

Because the add method expects an Int to come out of Holder and puts an Int back into the Holder, the type of the Holder must be invariant. That does not mean that invariant containers lose their ability to hold subclasses of their declared type. A Holder[Number] can contain a Double, and an Array[Object] can contain String, Integer, and so on. Let’s put a Double into a Holder[Number]:

scala> val nh = new Holder[Number](33.3d)

nh: Holder[java.lang.Number] = Holder@340c9c

And we define a method that rounds the number:

scala> def round(in: Holder[Number]) {in.data = in.data.intValue}

round: (Holder[java.lang.Number])Unit

We call the round method, and let’s see what we get out the other side:

scala> round(nh)


scala> nh.data

res16: java.lang.Number = 33

We put in a Number and got back a Number. What’s the underlying class for the Number?

scala> nh.data.getClass

res17: java.lang.Class[_] = class java.lang.Integer

Great. Integer is a subclass of Number, so we can put a Integer or a Double into the Holder[Number]. We preserve the ability to use class hierarchies with invariant type parameters. Let’s finally see what happens when we try to pass a Holder[Double] into round.

scala> val dh = new Holder(33.3d)

dh: Holder[Double] = Holder@1801e5f

scala> round(dh)

<console>:8: error: type mismatch;

found : Holder[Double]

required: Holder[java.lang.Number]

So, invariant type parameters protect us when we have mutable data structures like arrays. Let’s move on to covariant parameter types.

Covariant Parameter Types

Covariant parameter types are designated with a + before the type parameter. A covariant type is useful for read-only containers. Scala’s List is defined as List[+T], which means that it’s covariant on type T. List is covariant because if you pass a List[String] to a method that expects a List[Any], then every element of the List satisfies the requirement that is an Any and we cannot change the contents of the List.

Let’s define an immutable class, Getable. Once an instance of Getable is created, it cannot change, so we can mark its type, T, as covariant.

scala> class Getable[+T](val data: T)

defined class Getable

Let’s define a method that takes a Getable[Any]:

scala> def get(in: Getable[Any]) {println("It's "+in.data)}

get: (Getable[Any])Unit

We define an instance of Getable[String]:

scala> val gs = new Getable("String")

gs: Getable[java.lang.String] = Getable@10a69f0

We can call get with gs:

scala> get(gs)

It's String

Let’s try the same example but passing a Getable[java.lang.Double] into something that expects a Getable[Number]:

scala> def getNum(in: Getable[Number]) = in.data.intValue

getNum: (Getable[java.lang.Number])Int

scala> def gd = new Getable(new java.lang.Double(33.3))

gd: Getable[java.lang.Double]

scala> getNum(gd)

res7: Int = 33

Yes, the covariance works the way we expect it to. We can make read-only classes covariant. I guess that means that contravariance is good for write-only classes.

Contravariant Parameter Types

So, if covariance allows us to pass List[String] to a method that expects List[Any], what good is contravariance? Let’s first look at a write-only class, Putable:

scala> class Putable[-T] {

def put(in: T) {println("Putting "+in)}

}

Next, let’s define a method that takes a Putable[String]:

scala> def writeOnly(in: Putable[String]) {in.put("Hello")}

writeOnly: (Putable[String])Unit

And let’s declare an instance of Putable[AnyRef]:

scala> val p = new Putable[AnyRef]

p: Putable[AnyRef] = Putable@75303f

And what happens if we try to call writeOnly?

scala> writeOnly(p)

Putting Hello

Okay, so we can call a method that expects a Putable[String] with a Putable[AnyRef] because we are guaranteed to call the put method with a String, which is a subclass of AnyRef. Standing along, this is not particularly valuable, but if we have a class that does something with input that results in output, the value of contravariance becomes obvious. The inputs to a transformation are contravariant. Calling something that expects at least any AnyRef with a String is legal and valid. But the return value can be covariant because we expect to get back a Number, so if we get an Integer, a subclass of Number, we’re okay. Let’s see how it works. We’ll define DS with a contravariant In type and a covariant Out type:

scala> trait DS[-In, +Out]{def apply(i: In): Out}

defined trait DS

Let’s create an instance that will convert Any into an Int:

scala> val t1 = new DS[Any, Int]{def apply(i: Any) = i.toString.toInt}

t1: java.lang.Object with DS[Any,Int] = $anon$1@14dcfad

We define check, a method that takes a DS[String, Any]:

scala> def check(in: DS[String, Any]) = in("333")

check: (DS[String,Any])Any

And we call check with t1:

scala> check(t1)

res14: Any = 333

Rules of Variance

So, we’ve successfully defined and used an invariant type. The invariant type was mutable, so it both returned and was called with a particular type. We created a convariant type which was an immutable holder of a value. Finally, we created a transformer that had contravariant input and covariant output. Wait, that sounds like a function. That’s right, Scala’s FunctionN traits have contravariant parameters and covariant results. This leads us to the simple rules of variance:

* Mutable containers should be invariant.

* Immutable containers should be covariant.

* Inputs to transformations should be contravariant, and outputs from transformations should be covariant.



On Sat, Jun 13, 2009 at 9:24 AM, Mohamed Bana <mbana.lists@googlemail.com> wrote:
i think it's inferring the most general type to make it type check. maybe if you pass a type parameter

scala> val one = List[Int](1,2,"3")
<console>:4: error: type mismatch;
 found   : java.lang.String("3")
 required: Int
      val one = List[Int](1,2,"3")

nik_m wrote:
Hi, I am new to Scala and just started learning it.
I read that List are immuatbale sequence of objects of SAME type. But I
tried writing
a List containing differnt types and it compiles successfully.

val one = List(1,2,"3") // Is a List of both Int and String

Can anyone please explain this behavior ?



--
Lift, the simply functional web framework http://liftweb.net
Beginning Scala http://www.apress.com/book/view/1430219890
Follow me: http://twitter.com/dpp
Git some: http://github.com/dpp
Viktor Klang
Joined: 2008-12-17,
User offline. Last seen 1 year 27 weeks ago.
Re: Doubt regarding Lists
Unfortunately, noone can be told what variance is, you have to see it for yourself...remember...all im offering is the truth... nothing more.

On Sat, Jun 13, 2009 at 6:30 PM, David Pollak <feeder.of.the.bears@gmail.com> wrote:
This relates to variance.  Lists are covariant.  Here's the section from Beginning Scala on Variance:

Variance

Variance is an important and challenging concept. It defines the rules by which parameterized types can be passed as parameters. In the beginning of the chapter, we showed how passing a String[] (Java notation) to a method expecting an Object[] can cause problems. Java allows you to pass an array of something to a method expecting an array of something’s superclass. This is called covariance. On the surface, this makes a lot of sense. If you can pass a String to a method expecting an Object, why can’t you pass an Array[String] (Scala notation) to a method expecting an Array[Object]? Because Array is mutable: it can be written to in addition to being read from, so a method that takes an Array[Object] may modify the Array by inserting something that cannot be inserted into an Array[String]. Defining the type variance for type parameters allows you to control how parameterized types can be passed to methods.

Variance comes in three flavors: invariant, covariant, and contravariant. Type parameters can be individually marked as covariant or contravariant and are by default invariant.

Invariant Parameter Types

In Scala, Array[T] is invariant. This means that you can only pass an Array[String] to foo(a: Array[String]) and that you can only pass an Array[Object] to bar(a: Array[Object]). This ensures that what is read from or written to the array is something of the correct type. So, for anything that’s mutable, the type parameter should be invariant. You do this by doing nothing with the type parameter. So, let’s define an invariant class:

class Holder[T](var data: T)

The class holds data of type T. Let’s write a method:

scala> def add(in: Holder[Int]) {in.data = in.data + 1}

add: (Holder[Int])Unit

scala> val h = new Holder(0)

h: Holder[Int] = Holder@bc0eba

scala> add(h)


scala> h.data

res2: Int = 1

Because the add method expects an Int to come out of Holder and puts an Int back into the Holder, the type of the Holder must be invariant. That does not mean that invariant containers lose their ability to hold subclasses of their declared type. A Holder[Number] can contain a Double, and an Array[Object] can contain String, Integer, and so on. Let’s put a Double into a Holder[Number]:

scala> val nh = new Holder[Number](33.3d)

nh: Holder[java.lang.Number] = Holder@340c9c

And we define a method that rounds the number:

scala> def round(in: Holder[Number]) {in.data = in.data.intValue}

round: (Holder[java.lang.Number])Unit

We call the round method, and let’s see what we get out the other side:

scala> round(nh)


scala> nh.data

res16: java.lang.Number = 33

We put in a Number and got back a Number. What’s the underlying class for the Number?

scala> nh.data.getClass

res17: java.lang.Class[_] = class java.lang.Integer

Great. Integer is a subclass of Number, so we can put a Integer or a Double into the Holder[Number]. We preserve the ability to use class hierarchies with invariant type parameters. Let’s finally see what happens when we try to pass a Holder[Double] into round.

scala> val dh = new Holder(33.3d)

dh: Holder[Double] = Holder@1801e5f

scala> round(dh)

<console>:8: error: type mismatch;

found : Holder[Double]

required: Holder[java.lang.Number]

So, invariant type parameters protect us when we have mutable data structures like arrays. Let’s move on to covariant parameter types.

Covariant Parameter Types

Covariant parameter types are designated with a + before the type parameter. A covariant type is useful for read-only containers. Scala’s List is defined as List[+T], which means that it’s covariant on type T. List is covariant because if you pass a List[String] to a method that expects a List[Any], then every element of the List satisfies the requirement that is an Any and we cannot change the contents of the List.

Let’s define an immutable class, Getable. Once an instance of Getable is created, it cannot change, so we can mark its type, T, as covariant.

scala> class Getable[+T](val data: T)

defined class Getable

Let’s define a method that takes a Getable[Any]:

scala> def get(in: Getable[Any]) {println("It's "+in.data)}

get: (Getable[Any])Unit

We define an instance of Getable[String]:

scala> val gs = new Getable("String")

gs: Getable[java.lang.String] = Getable@10a69f0

We can call get with gs:

scala> get(gs)

It's String

Let’s try the same example but passing a Getable[java.lang.Double] into something that expects a Getable[Number]:

scala> def getNum(in: Getable[Number]) = in.data.intValue

getNum: (Getable[java.lang.Number])Int

scala> def gd = new Getable(new java.lang.Double(33.3))

gd: Getable[java.lang.Double]

scala> getNum(gd)

res7: Int = 33

Yes, the covariance works the way we expect it to. We can make read-only classes covariant. I guess that means that contravariance is good for write-only classes.

Contravariant Parameter Types

So, if covariance allows us to pass List[String] to a method that expects List[Any], what good is contravariance? Let’s first look at a write-only class, Putable:

scala> class Putable[-T] {

def put(in: T) {println("Putting "+in)}

}

Next, let’s define a method that takes a Putable[String]:

scala> def writeOnly(in: Putable[String]) {in.put("Hello")}

writeOnly: (Putable[String])Unit

And let’s declare an instance of Putable[AnyRef]:

scala> val p = new Putable[AnyRef]

p: Putable[AnyRef] = Putable@75303f

And what happens if we try to call writeOnly?

scala> writeOnly(p)

Putting Hello

Okay, so we can call a method that expects a Putable[String] with a Putable[AnyRef] because we are guaranteed to call the put method with a String, which is a subclass of AnyRef. Standing along, this is not particularly valuable, but if we have a class that does something with input that results in output, the value of contravariance becomes obvious. The inputs to a transformation are contravariant. Calling something that expects at least any AnyRef with a String is legal and valid. But the return value can be covariant because we expect to get back a Number, so if we get an Integer, a subclass of Number, we’re okay. Let’s see how it works. We’ll define DS with a contravariant In type and a covariant Out type:

scala> trait DS[-In, +Out]{def apply(i: In): Out}

defined trait DS

Let’s create an instance that will convert Any into an Int:

scala> val t1 = new DS[Any, Int]{def apply(i: Any) = i.toString.toInt}

t1: java.lang.Object with DS[Any,Int] = $anon$1@14dcfad

We define check, a method that takes a DS[String, Any]:

scala> def check(in: DS[String, Any]) = in("333")

check: (DS[String,Any])Any

And we call check with t1:

scala> check(t1)

res14: Any = 333

Rules of Variance

So, we’ve successfully defined and used an invariant type. The invariant type was mutable, so it both returned and was called with a particular type. We created a convariant type which was an immutable holder of a value. Finally, we created a transformer that had contravariant input and covariant output. Wait, that sounds like a function. That’s right, Scala’s FunctionN traits have contravariant parameters and covariant results. This leads us to the simple rules of variance:

* Mutable containers should be invariant.

* Immutable containers should be covariant.

* Inputs to transformations should be contravariant, and outputs from transformations should be covariant.



On Sat, Jun 13, 2009 at 9:24 AM, Mohamed Bana <mbana.lists@googlemail.com> wrote:
i think it's inferring the most general type to make it type check. maybe if you pass a type parameter

scala> val one = List[Int](1,2,"3")
<console>:4: error: type mismatch;
 found   : java.lang.String("3")
 required: Int
      val one = List[Int](1,2,"3")

nik_m wrote:
Hi, I am new to Scala and just started learning it.
I read that List are immuatbale sequence of objects of SAME type. But I
tried writing
a List containing differnt types and it compiles successfully.

val one = List(1,2,"3") // Is a List of both Int and String

Can anyone please explain this behavior ?



--
Lift, the simply functional web framework http://liftweb.net
Beginning Scala http://www.apress.com/book/view/1430219890
Follow me: http://twitter.com/dpp
Git some: http://github.com/dpp



--
Viktor Klang
Scala Loudmouth
Derek Williams
Joined: 2009-06-13,
User offline. Last seen 42 years 45 weeks ago.
Re: Doubt regarding Lists

The problem that you will run into when using a list like this is you can
only treat each member of the list as an Any. For example:

scala> val one = List(1,2,"3")
one: List[Any] = List(1, 2, 3)

scala> one map {_ * 2}
:6: error: value * is not a member of Any
one map {_ * 2}

But if you have all Ints:

scala> val two = List(1,2,3)
two: List[Int] = List(1, 2, 3)

scala> two map {_ * 2}
res5: List[Int] = List(2, 4, 6)

Of course there are ways to deal with this, but it is a bit more complex
then the above code.

DRMacIver
Joined: 2008-09-02,
User offline. Last seen 42 years 45 weeks ago.
Re: Doubt regarding Lists

2009/6/13 David Pollak :
> This relates to variance.  Lists are covariant.

Actually, it doesn't. You'll see the same problems with collections
which aren't covariant (you won't see it with arrays because of the
amusing details of Array,apply, but that's a coincidence. For example:

scala> Set(1, 2, "3")
res0: scala.collection.immutable.Set[Any] = Set(1, 2, 3)

David Pollak
Joined: 2008-12-16,
User offline. Last seen 42 years 45 weeks ago.
Re: Doubt regarding Lists


On Sat, Jun 13, 2009 at 1:44 PM, David MacIver <david.maciver@gmail.com> wrote:
2009/6/13 David Pollak <feeder.of.the.bears@gmail.com>:
> This relates to variance.  Lists are covariant.

Actually, it doesn't. You'll see the same problems with collections
which aren't covariant (you won't see it with arrays because of the
amusing details of Array,apply, but that's a coincidence. For example:

scala> Set(1, 2, "3")
res0: scala.collection.immutable.Set[Any] = Set(1, 2, 3)

David,

Yes, if you're building up a List[Any], you're going to get a List[Any] and the original post was doing just that.

However, my reading between the lines of the original post suggested to me that he was exploring the following:
scala> val list1 = List(1,2,3)
list1: List[Int] = List(1, 2, 3)

scala> val list2 = "4" :: list1
list2: List[Any] = List(4, 1, 2, 3)

He had list1 which was a List[Int]... all the same.  He adds something to the List and it changes the type of the List to List[Any], the one class that's a superclass to all the elements in the List.  That's where variance comes in.  Also, had the original post built the list:
1 :: 2 :: "3" :: Nil

He would have seen the variance issues as well (Nothing to String to Any to Any).

Thanks,

David


--
Lift, the simply functional web framework http://liftweb.net
Beginning Scala http://www.apress.com/book/view/1430219890
Follow me: http://twitter.com/dpp
Git some: http://github.com/dpp
DRMacIver
Joined: 2008-09-02,
User offline. Last seen 42 years 45 weeks ago.
Re: Doubt regarding Lists

2009/6/13 David Pollak :
>
>
> On Sat, Jun 13, 2009 at 1:44 PM, David MacIver
> wrote:
>>
>> 2009/6/13 David Pollak :
>> > This relates to variance.  Lists are covariant.
>>
>> Actually, it doesn't. You'll see the same problems with collections
>> which aren't covariant (you won't see it with arrays because of the
>> amusing details of Array,apply, but that's a coincidence. For example:
>>
>> scala> Set(1, 2, "3")
>> res0: scala.collection.immutable.Set[Any] = Set(1, 2, 3)
>
> David,
>
> Yes, if you're building up a List[Any], you're going to get a List[Any] and
> the original post was doing just that.
>
> However, my reading between the lines of the original post suggested to me
> that he was exploring the following:

Then it might perhaps have been useful to say so in your original
response. Telling someone "this relates to variance" when it doesn't
(and absolutely nothing in the original post does. The question could
have been asked verbatim with List replaced with an invariant
collection) is a rather cruel thing to do, given how many people seem
to get confused by variance and that the actual explanation is much
simpler.

David Pollak
Joined: 2008-12-16,
User offline. Last seen 42 years 45 weeks ago.
Re: Doubt regarding Lists


On Sat, Jun 13, 2009 at 2:25 PM, David MacIver <david.maciver@gmail.com> wrote:
2009/6/13 David Pollak <feeder.of.the.bears@gmail.com>:
>
>
> On Sat, Jun 13, 2009 at 1:44 PM, David MacIver <david.maciver@gmail.com>
> wrote:
>>
>> 2009/6/13 David Pollak <feeder.of.the.bears@gmail.com>:
>> > This relates to variance.  Lists are covariant.
>>
>> Actually, it doesn't. You'll see the same problems with collections
>> which aren't covariant (you won't see it with arrays because of the
>> amusing details of Array,apply, but that's a coincidence. For example:
>>
>> scala> Set(1, 2, "3")
>> res0: scala.collection.immutable.Set[Any] = Set(1, 2, 3)
>
> David,
>
> Yes, if you're building up a List[Any], you're going to get a List[Any] and
> the original post was doing just that.
>
> However, my reading between the lines of the original post suggested to me
> that he was exploring the following:

Then it might perhaps have been useful to say so in your original
response.

I answered the question that I understood the poster asked.  While you interpreted the original question to be one of type inferencing, I interpreted it to be one of "why does building a list change the type of the List?"  It's been my experience that trying to answer the question that someone wanted to ask, if they only had the understanding to ask the question, is very helpful.  Your experience may be different and by all means help people the way you find its best to help them.    
Telling someone "this relates to variance" when it doesn't
(and absolutely nothing in the original post does. The question could
have been asked verbatim with List replaced with an invariant
collection) is a rather cruel thing to do, given how many people seem
to get confused by variance and that the actual explanation is much
simpler.

Once again, it depends on what the poster was trying to learn.  I agree that variance is a tough concept, although, like monads, I also think its treated by some as far tougher than it need be.  Introducing newcomers to concept of variance at reasonable times is not the worst thing that could happen to a newbie.  And, the actual explanation... type inferencing and the shared superclass of String and Int is not very far away from variance.
So, I'll go out on a limb and suggest that perhaps you'd be better off spending your time explaining things to folks the way you think they'd be best able to absorb the information rather than being the teaching police and telling me how best to explain things.  Perhaps, the folks who have followed this thread would have benefited from a positive perspective ("I see this as a type inference issue").
Thanks,
David 


--
Lift, the simply functional web framework http://liftweb.net
Beginning Scala http://www.apress.com/book/view/1430219890
Follow me: http://twitter.com/dpp
Git some: http://github.com/dpp
DRMacIver
Joined: 2008-09-02,
User offline. Last seen 42 years 45 weeks ago.
Re: Doubt regarding Lists

2009/6/13 David Pollak :
>
>
> On Sat, Jun 13, 2009 at 2:25 PM, David MacIver
> wrote:
>>
>> 2009/6/13 David Pollak :
>> >
>> >
>> > On Sat, Jun 13, 2009 at 1:44 PM, David MacIver
>> > wrote:
>> >>
>> >> 2009/6/13 David Pollak :
>> >> > This relates to variance.  Lists are covariant.
>> >>
>> >> Actually, it doesn't. You'll see the same problems with collections
>> >> which aren't covariant (you won't see it with arrays because of the
>> >> amusing details of Array,apply, but that's a coincidence. For example:
>> >>
>> >> scala> Set(1, 2, "3")
>> >> res0: scala.collection.immutable.Set[Any] = Set(1, 2, 3)
>> >
>> > David,
>> >
>> > Yes, if you're building up a List[Any], you're going to get a List[Any]
>> > and
>> > the original post was doing just that.
>> >
>> > However, my reading between the lines of the original post suggested to
>> > me
>> > that he was exploring the following:
>>
>> Then it might perhaps have been useful to say so in your original
>> response.
>
> I answered the question that I understood the poster asked.  While you
> interpreted the original question to be one of type inferencing, I
> interpreted it to be one of "why does building a list change the type of the
> List?"  It's been my experience that trying to answer the question that
> someone wanted to ask, if they only had the understanding to ask the
> question, is very helpful.  Your experience may be different and by all
> means help people the way you find its best to help them.
>

I'm really struggling to see where you're coming from here.

The original poster gave an example and asked what was going on in this example.

What was going on in this example was that a type was being inferred
as Any. It is solely about type inference and the fact that Int and
String have a common supertype. Various people have already explained
what was going on, so I felt there was no need for me to elaborate.

You then come along and, without providing specifics as to how this
had any bearing on the problem, said "this has to do with variance"
and quoted a large chunk of text on the subject of variance.

But the original problem has nothing to do with variance. You can
demonstrate this by noting that a) the same behaviour is exhibited by
invariant collections and b) Exactly the same behaviour occurs with
Java generics. Therefore you are answering a question that was not
asked. This is fine, and can be useful, but you *framed* the answer
without any qualification as to how it had bearing on the original
question.

So your answer was not merely "reading between the lines" of the
original post. In the absence of an explanation of its relation to the
post it was simply a wrong, and highly confusing, answer.

By the way, on a tangentially related note, your rules of variance are
wrong. immutable collections are often not covariant. In particular
immutable sets can't be. Further there's no particular reason why a
collection can't be mutable and covariant, though I'm not aware of any
interesting examples of that happening.

Joshua.Suereth
Joined: 2008-09-02,
User offline. Last seen 32 weeks 5 days ago.
Re: Doubt regarding Lists
This conversation is bordering on kitten-worthy.  Might I ask this be taken off-list?

Sent from my iPhone
On Jun 13, 2009, at 5:52 PM, David Pollak <feeder.of.the.bears@gmail.com> wrote:



On Sat, Jun 13, 2009 at 2:25 PM, David MacIver < (david [dot] maciver [at] gmail [dot] com> wrote:
2009/6/13 David Pollak < (feeder [dot] of [dot] the [dot] bears [at] gmail [dot] com>:
>
>
> On Sat, Jun 13, 2009 at 1:44 PM, David MacIver < (david [dot] maciver [at] gmail [dot] com>
> wrote:
>>
>> 2009/6/13 David Pollak < (feeder [dot] of [dot] the [dot] bears [at] gmail [dot] com>:
>> > This relates to variance.  Lists are covariant.
>>
>> Actually, it doesn't. You'll see the same problems with collections
>> which aren't covariant (you won't see it with arrays because of the
>> amusing details of Array,apply, but that's a coincidence. For example:
>>
>> scala> Set(1, 2, "3")
>> res0: scala.collection.immutable.Set[Any] = Set(1, 2, 3)
>
> David,
>
> Yes, if you're building up a List[Any], you're going to get a List[Any] and
> the original post was doing just that.
>
> However, my reading between the lines of the original post suggested to me
> that he was exploring the following:

Then it might perhaps have been useful to say so in your original
response.

I answered the question that I understood the poster asked.  While you interpreted the original question to be one of type inferencing, I interpreted it to be one of "why does building a list change the type of the List?"  It's been my experience that trying to answer the question that someone wanted to ask, if they only had the understanding to ask the question, is very helpful.  Your experience may be different and by all means help people the way you find its best to help them.    
Telling someone "this relates to variance" when it doesn't
(and absolutely nothing in the original post does. The question could
have been asked verbatim with List replaced with an invariant
collection) is a rather cruel thing to do, given how many people seem
to get confused by variance and that the actual explanation is much
simpler.

Once again, it depends on what the poster was trying to learn.  I agree that variance is a tough concept, although, like monads, I also think its treated by some as far tougher than it need be.  Introducing newcomers to concept of variance at reasonable times is not the worst thing that could happen to a newbie.  And, the actual explanation... type inferencing and the shared superclass of String and Int is not very far away from variance.
So, I'll go out on a limb and suggest that perhaps you'd be better off spending your time explaining things to folks the way you think they'd be best able to absorb the information rather than being the teaching police and telling me how best to explain things.  Perhaps, the folks who have followed this thread would have benefited from a positive perspective ("I see this as a type inference issue").
Thanks,
David 


--
Lift, the simply functional web framework http://liftweb.net
Beginning Scala http://www.apress.com/book/view/1430219890
Follow me: http://twitter.com/dpp
Git some: http://github.com/dpp
Erik Engbrecht
Joined: 2008-12-19,
User offline. Last seen 3 years 18 weeks ago.
Re: Doubt regarding Lists
Perhaps we need a "scala-internal-squabble" list?  :-)

On Sat, Jun 13, 2009 at 6:16 PM, Josh Suereth <joshua.suereth@gmail.com> wrote:
This conversation is bordering on kitten-worthy.  Might I ask this be taken off-list?

Sent from my iPhone
On Jun 13, 2009, at 5:52 PM, David Pollak <feeder.of.the.bears@gmail.com> wrote:



On Sat, Jun 13, 2009 at 2:25 PM, David MacIver <david.maciver@gmail.comdavid.maciver@gmail.com> wrote:
2009/6/13 David Pollak <feeder.of.the.bears@gmail.comfeeder.of.the.bears@gmail.com>:
>
>
> On Sat, Jun 13, 2009 at 1:44 PM, David MacIver <david.maciver@gmail.comdavid.maciver@gmail.com>
> wrote:
>>
>> 2009/6/13 David Pollak <feeder.of.the.bears@gmail.comfeeder.of.the.bears@gmail.com>:
>> > This relates to variance.  Lists are covariant.
>>
>> Actually, it doesn't. You'll see the same problems with collections
>> which aren't covariant (you won't see it with arrays because of the
>> amusing details of Array,apply, but that's a coincidence. For example:
>>
>> scala> Set(1, 2, "3")
>> res0: scala.collection.immutable.Set[Any] = Set(1, 2, 3)
>
> David,
>
> Yes, if you're building up a List[Any], you're going to get a List[Any] and
> the original post was doing just that.
>
> However, my reading between the lines of the original post suggested to me
> that he was exploring the following:

Then it might perhaps have been useful to say so in your original
response.

I answered the question that I understood the poster asked.  While you interpreted the original question to be one of type inferencing, I interpreted it to be one of "why does building a list change the type of the List?"  It's been my experience that trying to answer the question that someone wanted to ask, if they only had the understanding to ask the question, is very helpful.  Your experience may be different and by all means help people the way you find its best to help them.    
Telling someone "this relates to variance" when it doesn't
(and absolutely nothing in the original post does. The question could
have been asked verbatim with List replaced with an invariant
collection) is a rather cruel thing to do, given how many people seem
to get confused by variance and that the actual explanation is much
simpler.

Once again, it depends on what the poster was trying to learn.  I agree that variance is a tough concept, although, like monads, I also think its treated by some as far tougher than it need be.  Introducing newcomers to concept of variance at reasonable times is not the worst thing that could happen to a newbie.  And, the actual explanation... type inferencing and the shared superclass of String and Int is not very far away from variance.
So, I'll go out on a limb and suggest that perhaps you'd be better off spending your time explaining things to folks the way you think they'd be best able to absorb the information rather than being the teaching police and telling me how best to explain things.  Perhaps, the folks who have followed this thread would have benefited from a positive perspective ("I see this as a type inference issue").
Thanks,
David 


--
Lift, the simply functional web framework http://liftweb.net
Beginning Scala http://www.apress.com/book/view/1430219890
Follow me: http://twitter.com/dpp
Git some: http://github.com/dpp



--
http://erikengbrecht.blogspot.com/
Kevin Wright
Joined: 2009-06-09,
User offline. Last seen 49 weeks 3 days ago.
Re: Doubt regarding Lists
I disagree... I think that this is a good place to mention variance
Conceptually, the following two expressions are equivalent:
    var x = List("1", 2, 3)
    var x = "1" :: 2 :: 3 :: Nil
If we're thinking functionally, then really we should consider the second expression to be the Right Way(tm) to think about list construction.
(and we'll ignore any optimisations that the library does involving builders, etc...)

This is important, as the second condition can also be written as
    var y = 2 :: 3 :: Nil;    var x = "1" :: y

Which is where variance comes in, as y is of type List[Int] though x is List[Any], thanks to the contravariant nature of ::



There's still one question that I haven't yet seen a definitive answer to.  Given the following expressions:
    var a = List("1", 2, 3)
    var b = "1" :: 2 :: 3 :: Nil
What are the respective types of a.tail and b.tail?  I'd hope that they were both List[Int], but wouldn't like to place money on it, certainly no in the first case!



On Sat, Jun 13, 2009 at 11:12 PM, David MacIver <david.maciver@gmail.com> wrote:
2009/6/13 David Pollak <feeder.of.the.bears@gmail.com>:
>
>
> On Sat, Jun 13, 2009 at 2:25 PM, David MacIver <david.maciver@gmail.com>
> wrote:
>>
>> 2009/6/13 David Pollak <feeder.of.the.bears@gmail.com>:
>> >
>> >
>> > On Sat, Jun 13, 2009 at 1:44 PM, David MacIver <david.maciver@gmail.com>
>> > wrote:
>> >>
>> >> 2009/6/13 David Pollak <feeder.of.the.bears@gmail.com>:
>> >> > This relates to variance.  Lists are covariant.
>> >>
>> >> Actually, it doesn't. You'll see the same problems with collections
>> >> which aren't covariant (you won't see it with arrays because of the
>> >> amusing details of Array,apply, but that's a coincidence. For example:
>> >>
>> >> scala> Set(1, 2, "3")
>> >> res0: scala.collection.immutable.Set[Any] = Set(1, 2, 3)
>> >
>> > David,
>> >
>> > Yes, if you're building up a List[Any], you're going to get a List[Any]
>> > and
>> > the original post was doing just that.
>> >
>> > However, my reading between the lines of the original post suggested to
>> > me
>> > that he was exploring the following:
>>
>> Then it might perhaps have been useful to say so in your original
>> response.
>
> I answered the question that I understood the poster asked.  While you
> interpreted the original question to be one of type inferencing, I
> interpreted it to be one of "why does building a list change the type of the
> List?"  It's been my experience that trying to answer the question that
> someone wanted to ask, if they only had the understanding to ask the
> question, is very helpful.  Your experience may be different and by all
> means help people the way you find its best to help them.
>

I'm really struggling to see where you're coming from here.

The original poster gave an example and asked what was going on in this example.

What was going on in this example was that a type was being inferred
as Any. It is solely about type inference and the fact that Int and
String have a common supertype. Various people have already explained
what was going on, so I felt there was no need for me to elaborate.

You then come along and, without providing specifics as to how this
had any bearing on the problem, said "this has to do with variance"
and quoted a large chunk of text on the subject of variance.

But the original problem has nothing to do with variance. You can
demonstrate this by noting that a) the same behaviour is exhibited by
invariant collections and b) Exactly the same behaviour occurs with
Java generics. Therefore you are answering a question that was not
asked. This is fine, and can be useful, but you *framed* the answer
without any qualification as to how it had bearing on the original
question.

So your answer was not merely "reading between the lines" of the
original post. In the absence of an explanation of its relation to the
post it was simply a wrong, and highly confusing, answer.

By the way, on a tangentially related note, your rules of variance are
wrong. immutable collections are often not covariant. In particular
immutable sets can't be. Further there's no particular reason why a
collection can't be mutable and covariant, though I'm not aware of any
interesting examples of that happening.

Kevin Wright
Joined: 2009-06-09,
User offline. Last seen 49 weeks 3 days ago.
Re: Doubt regarding Lists

of course, all those vars in that last post should have been vals
my bad!


I disagree... I think that this is a good place to mention variance
Conceptually, the following two expressions are equivalent:
    var x = List("1", 2, 3)
    var x = "1" :: 2 :: 3 :: Nil
If we're thinking functionally, then really we should consider the second expression to be the Right Way(tm) to think about list construction.
(and we'll ignore any optimisations that the library does involving builders, etc...)

This is important, as the second condition can also be written as
    var y = 2 :: 3 :: Nil;    var x = "1" :: y

Which is where variance comes in, as y is of type List[Int] though x is List[Any], thanks to the contravariant nature of ::



There's still one question that I haven't yet seen a definitive answer to.  Given the following expressions:
    var a = List("1", 2, 3)
    var b = "1" :: 2 :: 3 :: Nil
What are the respective types of a.tail and b.tail?  I'd hope that they were both List[Int], but wouldn't like to place money on it, certainly no in the first case!



On Sat, Jun 13, 2009 at 11:12 PM, David MacIver <david.maciver@gmail.com> wrote:
2009/6/13 David Pollak <feeder.of.the.bears@gmail.com>:
>
>
> On Sat, Jun 13, 2009 at 2:25 PM, David MacIver <david.maciver@gmail.com>
> wrote:
>>
>> 2009/6/13 David Pollak <feeder.of.the.bears@gmail.com>:
>> >
>> >
>> > On Sat, Jun 13, 2009 at 1:44 PM, David MacIver <david.maciver@gmail.com>
>> > wrote:
>> >>
>> >> 2009/6/13 David Pollak <feeder.of.the.bears@gmail.com>:
>> >> > This relates to variance.  Lists are covariant.
>> >>
>> >> Actually, it doesn't. You'll see the same problems with collections
>> >> which aren't covariant (you won't see it with arrays because of the
>> >> amusing details of Array,apply, but that's a coincidence. For example:
>> >>
>> >> scala> Set(1, 2, "3")
>> >> res0: scala.collection.immutable.Set[Any] = Set(1, 2, 3)
>> >
>> > David,
>> >
>> > Yes, if you're building up a List[Any], you're going to get a List[Any]
>> > and
>> > the original post was doing just that.
>> >
>> > However, my reading between the lines of the original post suggested to
>> > me
>> > that he was exploring the following:
>>
>> Then it might perhaps have been useful to say so in your original
>> response.
>
> I answered the question that I understood the poster asked.  While you
> interpreted the original question to be one of type inferencing, I
> interpreted it to be one of "why does building a list change the type of the
> List?"  It's been my experience that trying to answer the question that
> someone wanted to ask, if they only had the understanding to ask the
> question, is very helpful.  Your experience may be different and by all
> means help people the way you find its best to help them.
>

I'm really struggling to see where you're coming from here.

The original poster gave an example and asked what was going on in this example.

What was going on in this example was that a type was being inferred
as Any. It is solely about type inference and the fact that Int and
String have a common supertype. Various people have already explained
what was going on, so I felt there was no need for me to elaborate.

You then come along and, without providing specifics as to how this
had any bearing on the problem, said "this has to do with variance"
and quoted a large chunk of text on the subject of variance.

But the original problem has nothing to do with variance. You can
demonstrate this by noting that a) the same behaviour is exhibited by
invariant collections and b) Exactly the same behaviour occurs with
Java generics. Therefore you are answering a question that was not
asked. This is fine, and can be useful, but you *framed* the answer
without any qualification as to how it had bearing on the original
question.

So your answer was not merely "reading between the lines" of the
original post. In the absence of an explanation of its relation to the
post it was simply a wrong, and highly confusing, answer.

By the way, on a tangentially related note, your rules of variance are
wrong. immutable collections are often not covariant. In particular
immutable sets can't be. Further there's no particular reason why a
collection can't be mutable and covariant, though I'm not aware of any
interesting examples of that happening.


James Iry
Joined: 2008-08-19,
User offline. Last seen 1 year 23 weeks ago.
Re: Doubt regarding Lists
Tail on a List[A] returns a List[A].  http://www.scala-lang.org/docu/files/api/scala/List.html#tail

You can confirm this in the repl

scala> List("1", 2, 3).tail
res0: List[Any] = List(2, 3)

scala> ("1" :: 2 :: 3 :: Nil).tail
res1: List[Any] = List(2, 3)


On Sat, Jun 13, 2009 at 4:26 PM, Kevin Wright <kev.lee.wright@googlemail.com> wrote:




There's still one question that I haven't yet seen a definitive answer to.  Given the following expressions:
    var a = List("1", 2, 3)
    var b = "1" :: 2 :: 3 :: Nil
What are the respective types of a.tail and b.tail?  I'd hope that they were both List[Int], but wouldn't like to place money on it, certainly no in the first case!


Tony Morris 2
Joined: 2009-03-20,
User offline. Last seen 42 years 45 weeks ago.
Re: Doubt regarding Lists

In the spirit of DRMacIver's appeal to discontinue the spread of
misinformation, I am compelled to step in. This post is full of errors.

Kevin Wright wrote:
> I disagree... I think that this is a good place to mention variance
> Conceptually, the following two expressions are equivalent:
> var x = List("1", 2, 3)
> var x = "1" :: 2 :: 3 :: Nil
> If we're thinking functionally, then really we should consider the
> second expression to be the Right Way(tm) to think about list
> construction.
This is not true. If you're "thinking functionally" the expressions are
equivalent and may be reasoned about accordingly. If anything in this
regard, then substitute var with val.

> (and we'll ignore any optimisations that the library does involving
> builders, etc...)
>
> This is important, as the second condition can also be written as
> var y = 2 :: 3 :: Nil; var x = "1" :: y
>
> Which is where variance comes in, as y is of type List[Int] though x
> is List[Any], thanks to the contravariant nature of ::
I think you mean covariance not contravariance. But even if you did,
then it is untrue. It is not "thanks to" the fact that List is a
covariant type constructor -- it is because of Scala's type inferencing.
Nothing more or less and hopefully, most apparent that variance is a
different issue.

Hopefully this example might drive the point home

scala> case class InvariantList[A](as: List[A])
defined class InvariantList

scala> def boo[A](a: A*) = InvariantList[A](a.toList)
boo: [A](A*)InvariantList[A]

scala> boo("1", 2, 3)
res2: InvariantList[Any] = InvariantList(List(1, 2, 3))

>
>
>
> There's still one question that I haven't yet seen a definitive answer
> to. Given the following expressions:
> var a = List("1", 2, 3)
> var b = "1" :: 2 :: 3 :: Nil
> What are the respective types of a.tail and b.tail? I'd hope that
> they were both List[Int], but wouldn't like to place money on it,
> certainly no in the first case!
Any as per the type inferencer.

I hope this can help.

Kevin Wright
Joined: 2009-06-09,
User offline. Last seen 49 weeks 3 days ago.
Re: Doubt regarding Lists


On Sun, Jun 14, 2009 at 12:38 AM, Tony Morris <tonymorris@gmail.com> wrote:
In the spirit of DRMacIver's appeal to discontinue the spread of
misinformation, I am compelled to step in. This post is full of errors.

Kevin Wright wrote:
> I disagree... I think that this is a good place to mention variance
> Conceptually, the following two expressions are equivalent:
>     var x = List("1", 2, 3)
>     var x = "1" :: 2 :: 3 :: Nil
> If we're thinking functionally, then really we should consider the
> second expression to be the Right Way(tm) to think about list
> construction.
This is not true. If you're "thinking functionally" the expressions are
equivalent and may be reasoned about accordingly. If anything in this
regard, then substitute var with val.

Yeah, already spotted the var/val thing.  In my defence I'm a little sleep deprived right now thanks to the new-born...
 
> (and we'll ignore any optimisations that the library does involving
> builders, etc...)
>
> This is important, as the second condition can also be written as
>     var y = 2 :: 3 :: Nil;    var x = "1" :: y
>
> Which is where variance comes in, as y is of type List[Int] though x
> is List[Any], thanks to the contravariant nature of ::
I think you mean covariance not contravariance. But even if you did,
then it is untrue. It is not "thanks to" the fact that List is a
covariant type constructor -- it is because of Scala's type inferencing.
Nothing more or less and hopefully, most apparent that variance is a
different issue.
 Please correct me if I missed something vital here, but my understanding is that:

The list was instantiated with the type parameter [Int], yet the value we prepend is of type String
(which is treated as being of the type Any, this being the closest common superclass to Int and String)
also, the :: operator is right-associative (as it ends with a colon)
So we are calling a method on an object with [Int] as a type parameter that accepts a supertype of Int as the argument
is this not an example of contavariance in the argument to :: ?
 

Hopefully this example might drive the point home

scala> case class InvariantList[A](as: List[A])
defined class InvariantList

scala> def boo[A](a: A*) = InvariantList[A](a.toList)
boo: [A](A*)InvariantList[A]

scala> boo("1", 2, 3)
res2: InvariantList[Any] = InvariantList(List(1, 2, 3))


>
>
>
> There's still one question that I haven't yet seen a definitive answer
> to.  Given the following expressions:
>     var a = List("1", 2, 3)
>     var b = "1" :: 2 :: 3 :: Nil
> What are the respective types of a.tail and b.tail?  I'd hope that
> they were both List[Int], but wouldn't like to place money on it,
> certainly no in the first case!
Any as per the type inferencer.

I hope this can help.

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


*stiill* can't help thinking it seems intuitive that the concrete type of ("a" :: (1 :: 2 :: Nil)).tail should be List[Int]
(obviously the declared return type of List[A].tail should be List[A])

James Iry
Joined: 2008-08-19,
User offline. Last seen 1 year 23 weeks ago.
Re: Doubt regarding Lists
The expression 2::Nil has type List[Int], the expression 1::2::Nil also has type List[Int], but the expression ("a" :: (1 :: 2 :: Nil)) has type List[Any] so the expression ("a" :: (1 :: 2 :: Nil)).tail has type List[Any].

For a style of list that tracks the types of its members see "heterogenous lists".  They require some pretty hairy type level programming.  See http://jnordenberg.blogspot.com/2008/09/hlist-in-scala-revisited-or-scala.html



On Sat, Jun 13, 2009 at 5:40 PM, Kevin Wright <kev.lee.wright@googlemail.com> wrote:


*stiill* can't help thinking it seems intuitive that the concrete type of ("a" :: (1 :: 2 :: Nil)).tail should be List[Int]
(obviously the declared return type of List[A].tail should be List[A])


Tony Morris 2
Joined: 2009-03-20,
User offline. Last seen 42 years 45 weeks ago.
Re: Doubt regarding Lists

Kevin Wright wrote:
>
> Yeah, already spotted the var/val thing. In my defence I'm a little
> sleep deprived right now thanks to the new-born...

Been there done that :)
>
>
> > (and we'll ignore any optimisations that the library does involving
> > builders, etc...)
> >
> > This is important, as the second condition can also be written as
> > var y = 2 :: 3 :: Nil; var x = "1" :: y
> >
> > Which is where variance comes in, as y is of type List[Int] though x
> > is List[Any], thanks to the contravariant nature of ::
> I think you mean covariance not contravariance. But even if you did,
> then it is untrue. It is not "thanks to" the fact that List is a
> covariant type constructor -- it is because of Scala's type
> inferencing.
> Nothing more or less and hopefully, most apparent that variance is a
> different issue.
>
>
> Please correct me if I missed something vital here, but my
> understanding is that:
>
> The list was instantiated with the type parameter [Int], yet the value
> we prepend is of type String
> (which is treated as being of the type Any, this being the closest
> common superclass to Int and String)
> also, the :: operator is right-associative (as it ends with a colon)
> So we are calling a method on an object with [Int] as a type parameter
> that accepts a supertype of Int as the argument
> is this not an example of contavariance in the argument to :: ?
>
The list was instantiated with the type parameter [Nothing] i.e. Nil

scala> val x: List[Nothing] = Nil
x: List[Nothing] = List()

Then you prepended 3 and the least common supertype of Int and Nothing
is Int so then you had a List[Int]

Then you prepended 2 and the least common supertype of Int and Int is
Int so then you had a List[Int]

Then you prepended "1" and the least common supertype of Int and String
is Any so then you had a List[Any]

--------

Here is an example of contra-variance:

scala> trait ContraInt[-A] { def apply(a: A): Int }
defined trait ContraInt

scala> def k: ContraInt[Any] = error("todo")
k: ContraInt[Any]

scala> def boo(z: ContraInt[String]) = error("boo")
boo: (ContraInt[String])Nothing

scala> def p = boo(k)
p: Nothing

Since ContraInt is a contra-variant type constructor this allows me to
invoke the boo method with the value k, even though:
k: ContraInt[Any]
the argument to boo: ContraInt[String]

In other words, since ContraInt is contra-variant then if T is a subtype
of U then ContraInt[U] is a subtype of ContraInt[T]. In this case,
String is a subtype of Any therefore ContraInt[Any] is a subtype of
ContraInt[String].

This is a very different issue to the type inferencing that I described.

Note that List is a covariant (not contra-variant) type constructor.

Tony Morris 2
Joined: 2009-03-20,
User offline. Last seen 42 years 45 weeks ago.
Re: Doubt regarding Lists

Tony Morris wrote:
> Kevin Wright wrote:
>
>> Yeah, already spotted the var/val thing. In my defence I'm a little
>> sleep deprived right now thanks to the new-born...
>>
>
> Been there done that :)
>
>>
>>
>> > (and we'll ignore any optimisations that the library does involving
>> > builders, etc...)
>> >
>> > This is important, as the second condition can also be written as
>> > var y = 2 :: 3 :: Nil; var x = "1" :: y
>> >
>> > Which is where variance comes in, as y is of type List[Int] though x
>> > is List[Any], thanks to the contravariant nature of ::
>> I think you mean covariance not contravariance. But even if you did,
>> then it is untrue. It is not "thanks to" the fact that List is a
>> covariant type constructor -- it is because of Scala's type
>> inferencing.
>> Nothing more or less and hopefully, most apparent that variance is a
>> different issue.
>>
>>
>> Please correct me if I missed something vital here, but my
>> understanding is that:
>>
>> The list was instantiated with the type parameter [Int], yet the value
>> we prepend is of type String
>> (which is treated as being of the type Any, this being the closest
>> common superclass to Int and String)
>> also, the :: operator is right-associative (as it ends with a colon)
>> So we are calling a method on an object with [Int] as a type parameter
>> that accepts a supertype of Int as the argument
>> is this not an example of contavariance in the argument to :: ?
>>
>>
> The list was instantiated with the type parameter [Nothing] i.e. Nil
>
> scala> val x: List[Nothing] = Nil
> x: List[Nothing] = List()
>
> Then you prepended 3 and the least common supertype of Int and Nothing
> is Int so then you had a List[Int]
>
> Then you prepended 2 and the least common supertype of Int and Int is
> Int so then you had a List[Int]
>
> Then you prepended "1" and the least common supertype of Int and String
> is Any so then you had a List[Any]
>
> --------
>
> Here is an example of contra-variance:
>
> scala> trait ContraInt[-A] { def apply(a: A): Int }
> defined trait ContraInt
>
> scala> def k: ContraInt[Any] = error("todo")
> k: ContraInt[Any]
>
> scala> def boo(z: ContraInt[String]) = error("boo")
> boo: (ContraInt[String])Nothing
>
> scala> def p = boo(k)
> p: Nothing
>
> Since ContraInt is a contra-variant type constructor this allows me to
> invoke the boo method with the value k, even though:
> k: ContraInt[Any]
> the argument to boo: ContraInt[String]
>
> In other words, since ContraInt is contra-variant then if T is a subtype
> of U then ContraInt[U] is a subtype of ContraInt[T]. In this case,
> String is a subtype of Any therefore ContraInt[Any] is a subtype of
> ContraInt[String].
>
> This is a very different issue to the type inferencing that I described.
>
> Note that List is a covariant (not contra-variant) type constructor.
>
>

I might add that you may test for contra-variance by defining a
contra-variant map method:

trait ContraInt[-A] {
def apply(a: A): Int

def comap[B](f: B => A): ContraInt[B] = new ContraInt[B] { def
apply(b: B) = ContraInt.this(f(b)) }
}

You can test for covariance by defining a covariant map method:

trait IntFunctor[+A] {
def apply(n: Int): A

def map[B](f: A => B): IntFunctor[A] = new IntFunctor[B] { def
apply(n: Int) = f(IntFunctor.this(n)) }
}

Try to define map for ContraInt or comap for IntFunctor. Observe that it
is not possible. This has implications for variance regardless of what
has been annotated.

Note that this has nothing to do with the type inferencing that has been
described.

dcsobral
Joined: 2009-04-23,
User offline. Last seen 38 weeks 5 days ago.
Re: Doubt regarding Lists
Though the question was based on a List constructor, not on the :: method, one should not ignore the importance of covariance for the later to work.
The following simplified example won't compile if you remove the covariance. If you remove [B >: T] from the method MyList_::, then you can't use Zippo to compose lists with -- 1 MyList_:: Zippo will return a type mismatch.
Can anyone fix it without covariance?
  abstract class MyList[+T] {    def isEmpty : Boolean    def head : T    def tail : MyList[T]     def MyList_::[B >: T](x : B) : MyList[B] =    new MyList_::(x, this)  }    case object Zippo extends MyList[Nothing] {    override def isEmpty = true     override def head = error("Head of an empty list")    override def tail = error("Tail of an empty list")  }    case class MyList_::[+T](private val hd : T, private val tl : MyList[T]) extends MyList[T] {     override def isEmpty = false    override def head = hd    override def tail = tl  }


--
Daniel C. Sobral

Something I learned in academia: there are three kinds of academic reviews: review by name, review by reference and review by value.
James Iry
Joined: 2008-08-19,
User offline. Last seen 1 year 23 weeks ago.
Re: Doubt regarding Lists
Here's an invariant List type.  Less convenient no doubt, but still works.  Zippo just can't be a singleton.

  abstract class MyList[T] {
    def head : T
    def tail : MyList[T]
    def ::(x : T) : MyList[T] = Cons(x, this)
  }
 
  case class Zippo[T]() extends MyList[T] {
    override def head = error("Head of an empty list")
    override def tail = error("Tail of an empty list")
  }
 
  case class Cons[T](val head : T, val tail : MyList[T]) extends MyList[T]

scala> val x = 4 :: 3 :: Zippo()
x: Test.MyList[Int] = Cons(4,Cons(3,Zippo()))


On Sat, Jun 13, 2009 at 7:11 PM, Daniel Sobral <dcsobral@gmail.com> wrote:
Though the question was based on a List constructor, not on the :: method, one should not ignore the importance of covariance for the later to work.
The following simplified example won't compile if you remove the covariance. If you remove [B >: T] from the method MyList_::, then you can't use Zippo to compose lists with -- 1 MyList_:: Zippo will return a type mismatch.
def MyList_::[B >: T](x : B) : MyList[B]

Kevin Wright
Joined: 2009-06-09,
User offline. Last seen 49 weeks 3 days ago.
Re: Doubt regarding Lists
Definitely going to have to tighten up on my terminology here, as I've seen the terms contra/co-variance used to two different ways:

a) type parameters, can defined to be co [+T] or contra [-T] -variant at a class level

b) in a lot of literature, the terms conta and co-variant are also used to describe arguments to methods

Given this discussion, I'm starting to understand why I'm not seing the second usage in most scala documentation, it gets confusing! :)
Instead, I'll stick with the established conventions, i.e.:

List[+T]... "is covariant in T"
::[U >: T](x: U): List[U]... is constrained to be a supertype of T (with no mention of being contra-variant)



Another thought experiment:
val x = 1 :: 2 :: Nil
val y = "a" :: x
val z = y.tail

given that x is a List[Int] and z is a List[Any] should the expression x eq y then be:
- true
- false
- indeterminate, not specified, liable to change between language & library versions

Also, should it be considered legal to cast z to a List[Int] ?


On Sun, Jun 14, 2009 at 2:10 AM, Tony Morris <tonymorris@gmail.com> wrote:


Kevin Wright wrote:
>
> Yeah, already spotted the var/val thing.  In my defence I'm a little
> sleep deprived right now thanks to the new-born...

Been there done that :)
>
>
>     > (and we'll ignore any optimisations that the library does involving
>     > builders, etc...)
>     >
>     > This is important, as the second condition can also be written as
>     >     var y = 2 :: 3 :: Nil;    var x = "1" :: y
>     >
>     > Which is where variance comes in, as y is of type List[Int] though x
>     > is List[Any], thanks to the contravariant nature of ::
>     I think you mean covariance not contravariance. But even if you did,
>     then it is untrue. It is not "thanks to" the fact that List is a
>     covariant type constructor -- it is because of Scala's type
>     inferencing.
>     Nothing more or less and hopefully, most apparent that variance is a
>     different issue.
>
>
> Please correct me if I missed something vital here, but my
> understanding is that:
>
> The list was instantiated with the type parameter [Int], yet the value
> we prepend is of type String
> (which is treated as being of the type Any, this being the closest
> common superclass to Int and String)
> also, the :: operator is right-associative (as it ends with a colon)
> So we are calling a method on an object with [Int] as a type parameter
> that accepts a supertype of Int as the argument
> is this not an example of contavariance in the argument to :: ?
>
The list was instantiated with the type parameter [Nothing] i.e. Nil

scala> val x: List[Nothing] = Nil
x: List[Nothing] = List()

Then you prepended 3 and the least common supertype of Int and Nothing
is Int so then you had a List[Int]

Then you prepended 2 and the least common supertype of Int and Int is
Int so then you had a List[Int]

Then you prepended "1" and the least common supertype of Int and String
is Any so then you had a List[Any]

--------

Here is an example of contra-variance:

scala> trait ContraInt[-A] { def apply(a: A): Int }
defined trait ContraInt

scala> def k: ContraInt[Any] = error("todo")
k: ContraInt[Any]

scala> def boo(z: ContraInt[String]) = error("boo")
boo: (ContraInt[String])Nothing

scala> def p = boo(k)
p: Nothing

Since ContraInt is a contra-variant type constructor this allows me to
invoke the boo method with the value k, even though:
k: ContraInt[Any]
the argument to boo: ContraInt[String]

In other words, since ContraInt is contra-variant then if T is a subtype
of U then ContraInt[U] is a subtype of ContraInt[T]. In this case,
String is a subtype of Any therefore ContraInt[Any] is a subtype of
ContraInt[String].

This is a very different issue to the type inferencing that I described.

Note that List is a covariant (not contra-variant) type constructor.

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



Viktor Klang
Joined: 2008-12-17,
User offline. Last seen 1 year 27 weeks ago.
Re: Doubt regarding Lists


On Sun, Jun 14, 2009 at 10:51 AM, Kevin Wright <kev.lee.wright@googlemail.com> wrote:
Definitely going to have to tighten up on my terminology here, as I've seen the terms contra/co-variance used to two different ways:

a) type parameters, can defined to be co [+T] or contra [-T] -variant at a class level

b) in a lot of literature, the terms conta and co-variant are also used to describe arguments to methods

Given this discussion, I'm starting to understand why I'm not seing the second usage in most scala documentation, it gets confusing! :)
Instead, I'll stick with the established conventions, i.e.:

List[+T]... "is covariant in T"
::[U >: T](x: U): List[U]... is constrained to be a supertype of T (with no mention of being contra-variant)



Another thought experiment:
val x = 1 :: 2 :: Nil
val y = "a" :: x
val z = y.tail

given that x is a List[Int] and z is a List[Any] should the expression x eq y then be:
- true
- false
- indeterminate, not specified, liable to change between language & library versions

Since eq() tests referential equality, according to the Rule of Least Surprise and given the implementation of immutable lists, I'd assume that x eq z
 
Disclaimer: I've been wrong before. (However, I can't recall when)

;)



Also, should it be considered legal to cast z to a List[Int] ?


On Sun, Jun 14, 2009 at 2:10 AM, Tony Morris <tonymorris@gmail.com> wrote:


Kevin Wright wrote:
>
> Yeah, already spotted the var/val thing.  In my defence I'm a little
> sleep deprived right now thanks to the new-born...

Been there done that :)
>
>
>     > (and we'll ignore any optimisations that the library does involving
>     > builders, etc...)
>     >
>     > This is important, as the second condition can also be written as
>     >     var y = 2 :: 3 :: Nil;    var x = "1" :: y
>     >
>     > Which is where variance comes in, as y is of type List[Int] though x
>     > is List[Any], thanks to the contravariant nature of ::
>     I think you mean covariance not contravariance. But even if you did,
>     then it is untrue. It is not "thanks to" the fact that List is a
>     covariant type constructor -- it is because of Scala's type
>     inferencing.
>     Nothing more or less and hopefully, most apparent that variance is a
>     different issue.
>
>
> Please correct me if I missed something vital here, but my
> understanding is that:
>
> The list was instantiated with the type parameter [Int], yet the value
> we prepend is of type String
> (which is treated as being of the type Any, this being the closest
> common superclass to Int and String)
> also, the :: operator is right-associative (as it ends with a colon)
> So we are calling a method on an object with [Int] as a type parameter
> that accepts a supertype of Int as the argument
> is this not an example of contavariance in the argument to :: ?
>
The list was instantiated with the type parameter [Nothing] i.e. Nil

scala> val x: List[Nothing] = Nil
x: List[Nothing] = List()

Then you prepended 3 and the least common supertype of Int and Nothing
is Int so then you had a List[Int]

Then you prepended 2 and the least common supertype of Int and Int is
Int so then you had a List[Int]

Then you prepended "1" and the least common supertype of Int and String
is Any so then you had a List[Any]

--------

Here is an example of contra-variance:

scala> trait ContraInt[-A] { def apply(a: A): Int }
defined trait ContraInt

scala> def k: ContraInt[Any] = error("todo")
k: ContraInt[Any]

scala> def boo(z: ContraInt[String]) = error("boo")
boo: (ContraInt[String])Nothing

scala> def p = boo(k)
p: Nothing

Since ContraInt is a contra-variant type constructor this allows me to
invoke the boo method with the value k, even though:
k: ContraInt[Any]
the argument to boo: ContraInt[String]

In other words, since ContraInt is contra-variant then if T is a subtype
of U then ContraInt[U] is a subtype of ContraInt[T]. In this case,
String is a subtype of Any therefore ContraInt[Any] is a subtype of
ContraInt[String].

This is a very different issue to the type inferencing that I described.

Note that List is a covariant (not contra-variant) type constructor.

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






--
Viktor Klang
Scala Loudmouth
James Iry
Joined: 2008-08-19,
User offline. Last seen 1 year 23 weeks ago.
Re: Doubt regarding Lists
On Sun, Jun 14, 2009 at 1:51 AM, Kevin Wright <kev.lee.wright@googlemail.com> wrote:
Definitely going to have to tighten up on my terminology here, as I've seen the terms contra/co-variance used to two different ways:

a) type parameters, can defined to be co [+T] or contra [-T] -variant at a class level

b) in a lot of literature, the terms conta and co-variant are also used to describe arguments to methods


That's because they're the same thing.  Or, more correctly, variance of method arguments and results is a specific case of the more general concept.

Here's what I mean.  Scala's Function1 is defined as Function1[-A,+R] - the argument is contravariant and the return is covariant.  What that means is that

(A1 => R1) <: (A2 => R2) iff A1 >: A2 & R1 <: R2.  Notice how the arrow flips direction on the contravariant type but goes the same direction on the covariant type.

On methods, Java has covariant return types and invariant argument types.  That means that if you have a method with the signature "public R1 foo(A x)"  you can override it in a subclass with a method "public R2 foo(A x)" iff R2 <: R1.  So one way to look at is is to pretend that one argument Java methods conform to a Scala type Method1[A, +R] and that overriding a method is creating new method with a subtype of the type of the superclass method.  Scala works the same way.

Another OO language could go all the way and make method arguments contravariant.  In such a language the one argument method type would be Method1[-A, +R].  And that's exactly what Function1 looks like. Java and Scala cannot have contravariant argument types because of Java style method overloading. When you change method argument types the languages assume you are overloading and not overriding.

Copyright © 2012 École Polytechnique Fédérale de Lausanne (EPFL), Lausanne, Switzerland