I am going through a Scala book, and it states few concepts like:
Expression evaluates to a value
A value a an object
Expression has a type
A value doesn't have a type
Value is an object but it doesn't have a type.
How is it possible? In jvm, objects are of certain type, isn't?
The distinction here is that objects have a class, but it's not the same as a type. E.g.
val list = List(1, 2, 3)
The type of list is List[Int]; when you run the program, you can see in the debugger (or by calling list.getClass) that its class is something called $colon$colon (which corresponds to :: in Scala code).
val list2 = List.empty[Int]
val list3 = List("")
list2 has the same type as list1, but a different class; list3 has a different type, but the same class.
Related
Today is my first day of learning Scala, and one of the things I have learnt is that a common way to initialise lists combines Nil (an empty list) with :: (prepend method).
Let's say I initialise a list this way:
val myList = List("A", "B", "C")
which is the same as:
val myList = "A" :: "B" :: "C" :: Nil
I understand that you can read the second chunk as the following:
Start with an empty list
Add "C" to the beginning of that list. The list is no longer empty.
Add "B" to the beginning of the list.
Add "A" to the beginning of the list.
Assign the list to the immutable List collection. The data type of each list element is inferred (String).
What I don't understand is why doesn't Nil act the same as NULL? Why is there no NULL value at the end of the list when it is initialised in this way?
(Forgive me, OOP and FP are not my thing but I'd like to learn.)
If you want to be able to call methods on all lists including empty list (for example Nil.length), Nil must be an object, not null. Otherwise you'll have NullPointerException instead of 0.
Also it's necessary for type safety. Nil is a value of type List[Nothing], i.e. of List[A] for all A (because List is covariant). And null is a value of type Null i.e. of all reference types (because Null is a subtype of any reference type), not only of list types.
Too many things to learn. You first need to understand what is an object, what is a class, what is an interface / trait, what is a value, what is a type. Then with those, you may need to learn about ADTs.
I will try to give a quick answer to that.
The List datatype is defined as
sealed trait List[+A]
final case class ::[+A](head: A, tail: List[A]) extends List[A]
final case object Nil extends List[Nothing]
So here we can see three things: a trait, a class and an object.
A class is a blueprint of memory for creating objects (also called instances). It is not a value, for using it you have to call its constructor (in this case, it receives two values, the head and the tail) to get a new value of that class.
An object is like an anonymous class that is already instantiated, so it is already a value, which you can use as it is.
A trait is a like a class that can not be instantiated, it is said to be abstract. It is used to define common behaviors that all subclasses have to follow.
Also, when the trait is sealed and the classes and objects are cases, we call those ADTs (algebraic data types).
Do not let the fancy name to scare you, it just means that the trait represents a single type (om this case the List) and all the cases represents the parts of such type.
ADTs are formed of products and sums, sums represents alternatives (it is an A or a B) and products represents conjunctions (it is an A and a B).
In the case of the List we say that it is either the empty list (Nil) or a cons (::) which has its own head and tail, where the tail is another list.
With all that, and a the knowledge that Scala has a syntactic trick to allow symbolic names to be used in the middle, you can see that this:
val list = 1 :: 2 :: 3 :: Nil
Is the same as:
val list = new ::(
head = 1,
tail = new ::(
head = 2,
tail = new ::(
head = 3,
tail = Nil
)
)
)
Bonus:
There are more types than classes.
null its a value of type Null (the only value to habit such type), which is a subtype of all the AnyRef types. It is, however, a special kind of value; because it will fail if you try to operate with it in any way. My advice, forget that it exists, it was a mistake and it is only there for Java interop. If you need to model the absence of a value use Option.
Marting Odersky in his book writes:
Class ::, pronounced “cons” for “construct,” represents non-empty lists.
and
The list construction methods :: and ::: are special. Because they end
in a colon, they are bound to their right operand. That is, an
operation such as x :: xs is treated as the method call xs.::(x), not
x.::(xs). In fact, x.::(xs) would not make sense, as x is of the list
element type, which can be arbitrary, so we cannot assume that this
type would have a :: method. For this reason, the :: method should
take an element value and yield a new list
So is :: a method or a class?
It is both a class and a method. Cons is a type parametised class. List[A] has two sub classes: Cons and Nil. As Cons is a class it can be created by its constructor as follows:
val s = new ::[Int](4, Nil)
Cons is a case class and we use the constructor when we do a pattern match. Cons is also a method on the list class that is implemented in its two sub classes. Hence we can use the cons method on the cons class that we have created above.
val s1 = s.::(5)
Part of the confusion may arise, because we normally create Lists using the apply method of the List object:
val s2 = List(1, 2, 3)
Normally the apply method of an object returns a new instance of the Class with the same name as an object. This is however just convention. In this particular case the List Object returns a new instance of the Cons subclass. The List class itself is a sealed abstract class so can not be instantiated. So the above apply method does the following:
val s2 = 1 :: 2 :: 3 :: Nil
Any method that ends in a ':' is a method on the operand to its right so it can be rewritten as
val s2 = 1 :: (2 :: (3 :: Nil)) //or as
val s2 = Nil.::(3).::(2).::(1)
So the Cons(::) method on the Nil object takes the 3 as a parameter and produces an anonymous instantiation of the Cons class with 3 as its head and a reference to the Nil object as its tail. Lets call this anonymous object c1. The Cons method is then called on c1 taking 2 as its parameter returning a new anonymous Cons instantiation lets call it c2, which has 2 for its head and a reference to c1 as its tail. Then finally the cons method on the c2 object takes the 1 as a parameter and returns the named object s2 with 1 as its head and a reference to c2 as its tail.
A second point of confusion is that the REPL and Scala worksheets use a class' toString methods to display results. So the worksheet gives us:
val s3 = List(5, 6, 7) // s3 : List[Int] = List(5, 6, 7)
val s4 = List() // s4 : List[Nothing] = List()
val s5: List[Int] = List() // s5 : List[Int] = List()
s3.getClass.getName // res3: String = scala.collection.immutable.$colon$colon
s4.getClass.getName // res4: String = scala.collection.immutable.Nil$
s5.getClass.getName // res5: String = scala.collection.immutable.Nil$
As said above List is sealed so new sub sub classes can not be created as Nil is an object and Cons is final. As Nil is an object it can't be parametrised. Nil inherits from List[Nothing]. At first glance that doesn't sound that useful but remember these data structures are immutable, so we can never add to them directly and Nothing is a sub class of every other class. So we can add the Nil class (indirectly) to any List without problem. The Cons class has two members a head and another List. Its a rather neat solution when you clock it.
I'm not sure if this has any practical use but you can use Cons as a type:
var list: ::[Int] = List (1,2,3).asInstanceOf[::[Int]]
println("Initialised List")
val list1 = Nil
list = list1.asInstanceOf[::[Int]] //this will throw a java class cast exception at run time
println("Reset List")
Short answer: both.
There is a subclass of list called ::, but you won't refer to it explicitly very often.
When you write e.g. 1 :: 2 :: Nil, the :: is a method on List, which creates an instance of the class :: behind the scenes.
:: is best thought of not as a method or a class, though, but as a constructor in the algebraic data type (ADT) sense. Wikipedia calls ADT constructors "quasi-functional entit[ies]", which makes them sound more complicated than they are, but isn't necessarily a bad way to think about them.
List has two constructors, :: (called cons in some languages) and Nil. The Wikipedia article I've linked above gives a good introduction to the idea of lists as algebraic data types.
It's worth noting that in some languages (like Haskell) ADT constructors don't have their own types associated with them—they are just functions that create an instance of the ADT.
This is typically effectively the case in Scala as well, where it's fairly rare to refer to the type of an ADT constructor like ::. It is possible, though—we can write this:
def foo(xs: ::[Int]) = ???
Or this (where Some is one of the constructors for the Option ADT).
def bar(s: Some[Int]) = ???
But this is in general not very useful, and could be considered an artifact of the way Scala implements algebraic data types.
I am playing around with collections in Scala and found that mutable collections are defined as invariant and immutable collections are defined as covariant. What is the relation between variance and mutability / immutability in Scala?
class Array[T]
class List[+T]
I had found an easy explanation in SIA. Following is straight from there.
Mutable objects need to be invariant
A type parameter is invariant when it’s neither covariant nor contravariant. All Scala mutable collection classes are invariant. An example can explain why mutable objects need to be invariant. Because ListBuffer is mutable, it’s declared as invariant as follows:
final class ListBuffer[A] ...{ ... }
Because it’s declared as invariant, you can’t assign ListBuffer from one type to another. The following code will throw a compilation error:
scala> val mxs: ListBuffer[String] = ListBuffer("pants")
mxs: scala.collection.mutable.ListBuffer[String] =
ListBuffer(pants)
scala> val everything: ListBuffer[Any] = mxs
<console>:6: error: type mismatch;
found : scala.collection.mutable.ListBuffer[String]
required: scala.collection.mutable.ListBuffer[Any]
val everything: ListBuffer[Any] = mxs
Even though String is a subtype of scala.Any, Scala still doesn’t let you assign mxs to everything. To understand why, assume ListBuffer is covariant and the following code snippet works without any compilation problem:
scala> val mxs: ListBuffer[String] = ListBuffer("pants")
mxs: scala.collection.mutable.ListBuffer[String] =
ListBuffer(pants)
scala> val everything: ListBuffer[Any] = mxs
scala> everything += 1
res4: everything.type = ListBuffer(1, pants)
Can you spot the problem? Because everything is of the type Any, you can store an integer value into a collection of strings. This is a disaster waiting to happen. It’s exactly what happens to Java arrays. To avoid these kinds of problems, it’s always a good idea to make mutable objects invariant. The next question is what happens in case of an immutable object for collections. It turns out that for immutable objects, covariance isn’t a problem at all. If you replace ListBuffer with the immutable List, you can take an instance of List[String] and assign it to List[Any] with- out a problem.
scala> val xs: List[String] = List("pants")
xs: List[String] = List(pants)
scala> val everything: List[Any] = xs
everything: List[Any] = List(pants)
The only reason this assignment is safe is because List is immutable. You can add 1 to xs List, and it will return a new List of type Any.
scala> 1 :: xs
res5: List[Any] = List(1, pants)
Again, this addition is safe because the cons(::) method always returns a new List, and its type is determined by the type of elements in the List. The only type that could store an integer value and reference value is scala.Any. This is an important property to remember about type variance when dealing with mutable/ immutable objects.
The best way to understand contravariance is to see the problem that comes when it’s absent. Try to spot the problem in the following Java code example:
Object[] arr = new int[1];
arr[0] = "Hello, there!";
You end up assigning the string to an integer array. Java catches this error at runtime by throwing an ArrayStoreException. Scala stops these kinds of errors at compile time by forcing parameter types to be either contravariant or invariant.
Hope this helps.
A type can only be marked covariant if that type parameter only appears in covariant positions. Generally, this means that the class/trait/object has methods that returns values of the variant type but does not have methods with parameters of the variant type. Mutable collections always have methods with parameters of the variant type, e.g. update. Imagine what would happen if Array could be declared Array[+T]:
val as = Array[String]("a string")
// this statement won't typecheck in actual Scala
val aa: Array[AnyRef] = as
aa(0) = ("I'm a string...", "but this tuple itself isn't!")
// Tuples don't have a substring method, so this would fail at run-time
// if the compiler allowed this code to compile.
as(0).substring(0)
I am trying to access a list object inside of another list with a specific type
val x = List ("item1" , "item2" , List ("a","b","c"))
val list_from_x :List [String]= x(2) // producing error
I need my list_from_x of type to be of List [String]
any idea how to I do such conversion ?
So x is of type List[Object]**, because you've used two different (otherwise unrelated) classes as elements of that list.
You'll be unable to access elements other than of type Any without a type cast:
val listFromX = x(2).asInstanceOf[List[String]]
This is not type-safe, so you'll want to check element types (x(2).isInstanceOf[List[_]]) or pattern matching.
** Use the REPL to verify this; though val x: List[Any] = List ("item1" , "item2" , List ("a,b,c")) works
HList to the rescue:
import shapeless._
import Nat._
val x = "item1" :: "item2" :: List("a,b,c") :: HNil
val list_from_x = x(_2)
list_from_x has the type List[String]
See the shapeless github page for more info.
As other answers have mentioned, a List can only hold elements of a certain type. For example, a List[String] can only hold Strings. If you put elements of mixed types into a List, then the parametrised type of the List will become the most specific type common to all the elements.
The most specific type common to String and List is Any. So in this example, x is of type List[Any]:
val x = List("item1" , "item2" , List ("a,b,c"))
HList can hold elements of different types and maintain each element's type.
Scala compiler will simply say you got Objects (or may be Any) inside list x. That's why. You have to cast the 3rd element into a List in order to assign into a List variable.
The issue is List is defined as List[+A] (source). Which means List takes a type parameter of co-variant type. (You can think of co-variance as type converting from narrow to broader. For ex: if Dog extends Animal and if you do Animal a = new Dog(), you have a Dog (narrow) converting to Animal (broader))
x is of type List[Object]. Doing
scala> val x = List ("item1" , "item2" , List ("a,b,c"))
x: List[Object] = List(item1, item2, List(a,b,c))
val list_from_x :List [String]= x(2)
is ultimately converting from List[Object] (broader) to List[String] (narrow). Which is contra-variant. Hence the compiler threw error because List is meant to be co-variant.
For ex: the below is completely valid. Thanks to co-variance:
scala> val x = List("hello")
x: List[String] = List(sdf)
scala> val o:List[Object] = x
o: List[Object] = List(sdf)
More about it here and here
I know that when you type:
val list = List(2,3)
you are accessing the apply method of the List object which returns a List. What I can't understand is why is this possible when the List class is abstract and therefore cannot be directly instanciated(new List() won't compile)?
I'd also like to ask what is the difference between:
val arr = Array(4,5,6)
and
val arr = new Array(4, 5, 6)
The List class is sealed and abstract. It has two concreate implementations
Nil which represents an empty list
::[B] which represents a non empty list with head and tail. ::[B] in the documentation
When you call List.apply it will jump through some hoops and supply you with an instance of the ::[B] case class.
About array: new Array(4, 5, 6) will throw a compile error as the constructor of array is defined like this: new Array(_length: Int). The apply method of the Array companion object uses the arguments to create a new instance of an Array (with the help of ArrayBuilder).
I started writing that the easy way to determine this is to look at the sources for the methods you're calling, which are available from the ScalaDoc. However, the various levels of indirection that are gone through to actually build a list give lie to the term 'easy'! It's worth having a look through if you want, starting from the apply method in the List object which is defined as follows:
override def apply[A](xs: A*): List[A] = xs.toList
You may or may not know that a parameter of the form xs : A* is treated internally as a Seq, which means that we're calling the toList method on a Seq, which is defined in TraversableOnce. This then delegates to a generic to method, which looks for an implicit
CanBuildFrom which actually constructs the list. So what you're getting back is some implementation of List which is chosen by the CanBuildFrom. What you actually get is a scala.collection.immutable.$colon$colon, which implements a singly-linked list.
Luckily, the behaviour of Array.apply is a little easier to look up:
def apply[T: ClassTag](xs: T*): Array[T] = {
val array = new Array[T](xs.length)
var i = 0
for (x <- xs.iterator) { array(i) = x; i += 1 }
array
}
So, Array.apply just delegates to new Array and then sets elements appropriately.