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?
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
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
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.
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
.