List and Tuples in Scala

2020-05-06 08:04发布

From the book 'Programming in Scala' by Martin Odersky:

Another useful container object is the tuple. Like lists, tuples are immutable, but unlike lists, tuples can contain different types of elements.

But I can have:

val oneTwoThreee = List(1, 2, "Third Element") //same as:List.apply(1,2,3)
for (i <- 0 to 2) {
  println(oneTwoThreee.apply((i)))
}

And its output is:

1 
2
Third Element

So List in Scala can have different types of elements.

And from same book:

You may be wondering why you can’t access the elements of a tuple like the elements of a list, for example, with “pair(0)”. The reason is that a list’s apply method always returns the same type, but each element of a tuple may be a different type:

But as above code shows, List.apply() can return different types.

Am I missing something here regarding List and Tuples in Scala?

4条回答
贼婆χ
2楼-- · 2020-05-06 08:42

But I can have:

val oneTwoThreee = List(1, 2, "Third Element") //same as:List.apply(1,2,3)
for (i <- 0 to 2) {
  println(oneTwoThreee.apply((i)))
}

And its output is:

1 
2
Third Element

So List in Scala can have different types of elements.

No, it can't. The type of your list is List[Any], so all elements are of the same type: Any.

If you type your code into the Scala REPL, it will tell you at each step what the types are:

scala> val oneTwoThreee = List(1, 2, "Third Element") //same as:List.apply(1,2,3)
oneTwoThreee: List[Any] = List(1, 2, Third Element)
              ↑↑↑↑↑↑↑↑↑

You can also always ask the Scala REPL for the type:

scala> :type oneTwoThreee
List[Any]

Any is a very general, and thus very useless type, since it doesn't have any "interesting" methods. In fact, you are doing pretty much the only thing you can do with an Any: representing it as a String. That's why you're not noticing the problem, you accidentally happened to pick the only thing that works.

Try multiplying the first and second element of your list:

oneTwoThreee(0) * oneTwoThreee(1)
// error: value * is not a member of Any
       oneTwoThreee(0) * oneTwoThreee(1)
                       ^

You may be wondering why you can’t access the elements of a tuple like the elements of a list, for example, with “pair(0)”. The reason is that a list’s apply method always returns the same type, but each element of a tuple may be a different type: But as above code shows, List.apply() can return different types.

No, it can't. Again let's just ask the Scala REPL what the types are:

oneTwoThreee(0)
//=> res: Any = 1
//        ↑↑↑

oneTwoThreee(1)
//=> res: Any = 2
//        ↑↑↑

oneTwoThreee(2)
//=> res: Any = Third Element
//        ↑↑↑

As you can see, the type is always the same: Any.

查看更多
虎瘦雄心在
3楼-- · 2020-05-06 08:46

Am I missing something here regarding List and Tuples in Scala?

I think the main point Odersky is trying to show is that each tuple element can contain its own individual type, which allows using multiple different types. Something that a List can't do because a list is homogeneous, meaning if you want a List[Int], all elements of that list must be Int values.

If you look at the type of the list you created, you'll see that the compiler infers List[Any], which is the common supertype of all Scala types. This means that if you want to do something concrete with the one of the elements in the list, i.e. it's head element which is of type Int, you can't because all the compiler knows about that element is that its of type Any, and you'll need to some how extract the underlying "concrete" type:

scala> val oneTwoThreee = List(1,2,"Third Element")
oneTwoThreee: List[Any] = List(1, 2, Third Element)

While using a Tuple3[Int, Int, String], actually "keeps" the concrete types:

scala> val tup = (1, 2, "Third Element")
tup: (Int, Int, String) = (1,2,Third Element)

Now if we want to extract one of the Int values and increment them by 1, we can:

scala> tup.copy(tup._1 + 1)
res1: (Int, Int, String) = (2,2,Third Element)

If we tried doing the same with a List[Any], the compiler would rightfully complain:

scala> oneTwoThreee.head + 1
<console>:13: error: type mismatch;
 found   : Int(1)
 required: String
       oneTwoThreee.head + 1
                           ^

The error is somewhat misleading, but this happens due to the fact head is actually of type Any.

There is a more advanced possibility of using heterogeneous lists using shapeless and it's HList data type:

import shapeless._

object Tests {
  def main(args: Array[String]): Unit = {
    val hList = 1 :: 2 :: "Third" :: HNil

    println(hList.head + 1)
  }
}

Which yields:

2
查看更多
Emotional °昔
4楼-- · 2020-05-06 08:49

In very short:

  • In a list, all elements are of the same type (even if it's the all-encompassing Any type).
  • In a tuple, each element has its own type.

With a list, you cannot require that the first element must be a string, and the second, a number. With a tuple, you can, and the compiler will statically check that.

What follows is that you can have a list of an arbitrary length, because all elements are alike, but a tuple can be only of a fixed length, with the type of each element declared separately.

If you come from e.g. C background, tuples are like structs, and lists are like arrays.

查看更多
够拽才男人
5楼-- · 2020-05-06 09:00

A very simple demonstration of what the other answers are telling you.

val tuplX = (10, "ten")      //tuplX: (Int, String) = (10,ten)
val listX = List(10, "ten")  //listX: List[Any] = List(10, ten)

tuplX._1 - 6     //res0: Int = 4
tuplX._2.length  //res1: Int = 3

listX(0) - 6     //Error: value - is not a member of Any
listX(1).length  //Error: value length is not a member of Any
查看更多
登录 后发表回答