Scala Tutorials Part #7 - Objects Everywhere
Originally Posted On : 29 Dec 2016 Last Updated : 27 Aug 2018
Objects Everywhere
In this post we discuss on why the concept of an object in scala is more prevalent and what are its advantages. Examples are mostly with data types since they are fundamental to the language.
This is part 7 of the scala tutorial series. Check here for the full series.
: This article has been translated to chinese by ChanZong Huang, you can check it out here
Index
- Introduction
- Everything is an expression
- Data types as Objects
- Operations on types
- Creation of custom types
- Language constructs vs Library Abstractions
- Java’s data type boxing/unboxing compared
- Bigint example
- Typecasting
- Value comparison
- Implementations in other languages and some notes
Introduction
Scala is a multi-paradigm language, mainly a combination of object oriented and functional. To support this kind of a language, it is necessary to formulate an idea around which things are built. As we saw earlier, in scala there are no primitive types and everything is an object. This is not be confused with singleton objects which we saw in part 4, but object as in instance of a class. In the rest of this article an object means an instance of a class an in regular java speak.
We have seen before that these two concepts i.e object oriented and functional are kind of orthogonal to each other. I am not going to give a exhaustive list on what are all objects and what are not, but I will be explaining the idea and advantages behind it and why it is important.
Everything is an expression
In Java, there is a difference between an expression and a statement.
int value = 10;
boolean result;
if(value > 20) {
result = true;
}
else {
result = false;
}
The if statements are actual statements i.e they do something while expressions on the other hand evaluate to a value.
In Scala, almost everything(there are some exceptions) evaluates to an expression.
val number = 20
val s = {
if(number > 10){
println("Number is greater")
}
else {
println("Number is lesser")
}
"testing"
}
println(s)
If you run the above code it prints out the following,
Number is greater
testing
This means two things, first the code inside the variable declaration for s
actually ran and printed some values and second the value “testing” is actually assigned to the variable s
. Similar to the return on Scala methods, the last line of the code block is taken as the value.
In future articles, you will see that even loops and match statements(pattern matching) evaluate in the same way as code blocks do.Now we have established that everything is an expression in Scala, it also means that it evaluates to something i.e an Object.
Data types as Objects
There are no native data types in scala and all of the data types have a class of their own. Now how would you go by designing something as fundamental as a data type ? Turns out that all of the data types map to native data types in java whenever it seems appropriate. We will take a look at one example i.e Int since it is simpler to explain and the same idea extends to almost all of the types.
You can try decompiling a class containing an Int
and see it compiles to public static int
i.e the java primitive type int.
All of the data types in scala except String
are present inside the package scala
Below is a list of data types that convert directly to native types on the JVM
Scala type | Runtime mapping |
---|---|
Int.scala | int |
Short.scala | short |
Double.scala | double |
Long.scala | long |
Byte.scala | byte |
Float.scala | float |
Boolean.scala | boolean |
Char.scala | char |
Scala variables do not have any additional overhead of creating objects, they all map to a native type in the JVM.
Operations on types
We have understood that all base types are objects in scala. The next important thing that comes to mind is that how will one do operations on it ? Since adding two objects is not possible, scala resorts to something called synthetic methods which we saw earlier in case classes.
All operations that we do in primitive java types such as +
,-
,*
etc ., are implemented as methods. Let’s take Int.scala for example and look at how addition is implemented.
/** Returns the sum of this value and `x`. */
def +(x: Byte): Int
/** Returns the sum of this value and `x`. */
def +(x: Short): Int
/** Returns the sum of this value and `x`. */
def +(x: Char): Int
/** Returns the sum of this value and `x`. */
def +(x: Int): Int
/** Returns the sum of this value and `x`. */
def +(x: Long): Long
/** Returns the sum of this value and `x`. */
def +(x: Float): Float
/** Returns the sum of this value and `x`. */
def +(x:Double): Double
Many things can be inferred from the above code snippet and Int.scala
.
- Above code sample defines all of the possible addition operations for other types with the
Int
type. Other type operations forBoolean
,Double
etc have the same kind of logic. - The above definition is inside of an abstract class and it is final hence cannot be extended.
- As we saw in the second part, all the variables with value types extend
AnyVal
which again extends fromAny
.
All of the mentioned methods which define the fundamental operators are abstract. So how does it get implemented ?
If you try the same with regular classes you will get an error of course.
abstract class CustomVariable {
def +(x: Byte): Int
}
So how does it work and gets translated to a native type ? It’s all compiler magic. As usual, lets understand by de-compiling a few classes. Remember that scala type classes have special meaning since they follow a hierarchy and that is the reason why the Int.scala is abstract but still we are able to use it.
Let’s consider the below class with a custom +
function implemented.
class CustomVariable {
def +(y:Int) : Int = {
this+y
}
}
This gets compiled as below.
public class CustomVariable {
public int $plus(int);
Code:
0: aload_0
1: iload_1
2: invokevirtual #12 // Method $plus:(I)I
5: ireturn
public CustomVariable();
Code:
0: aload_0
1: invokespecial #20 // Method java/lang/Object."<init>":()V
4: return
}
The +
gets compiled to $plus
since the former is not a legal definition as per java.
Let’s see another example.
class CustomVariable {
def add_1(x: Int, y: Int) = x + y
def add_2(x: Int, y: Int) = (x).+(y)
}
It gets decompiled as,
public class CustomVariable {
public int add_1(int, int);
Code:
0: iload_1
1: iload_2
2: iadd
3: ireturn
public int add_2(int, int);
Code:
0: iload_1
1: iload_2
2: iadd
3: ireturn
public CustomVariable();
Code:
0: aload_0
1: invokespecial #20 // Method java/lang/Object."<init>":()V
4: return
}
The +
and .+
gets compiled to same to the iadd
operation.
This way of calling a class member without the dot operator is called
infix notation which we see later in a dedicated tutorial.
Since the magic is done while compilation, the +
by itself is represented as a synthetic function.
Creation of custom types
We looked at how Int.scala
was coded. But how would you go about creating something fundamental as this without the help of the compiler.
If you took the course in Coursera “Functional Programming principles in Scala - by Martin Odersky”, then you would be familiar with this. Nonetheless, below is the video where he explains it in a succinct way.
If by any chance the above listed youtube video is taken down, just sign up for the course in Coursera, even if you don’t need the certificate you can audit the course/see the videos. This video is listed as Lecture 4.1 in the course.
There might be some parts of the video that you might not fully understand, just ignore them for them, I will definitely cover them later.
This should give you an intuition on why such a choice was made in scala.
Language constructs vs Library Abstractions
In java the operations such as +
are built within the language which makes them difficult for customization.
Scala was built with such things in mind. In fact the name scala is derived from a portmanteau of the words “Scalable language” implying that it grows with the users demand. The term scalable does not necessarily mean it scales for performance, but rather a extensible/scalable as a language.
This will become more coherent with how the BigInt
class behaves in scala when compared to java as explained in the following sections.
Java's data type boxing/unboxing compared
Unlike scala, java has both primitive and boxed types. This is kind of ugly when you compare it with scala since everything is an object here.
We already saw how scala types convert to native JVM primitive types, but how do they work in java ?
Let’s take the below class for example.
public class Test {
public static void main(String[] args) {
Integer i = 10;
Integer k = 30;
System.out.println(i+k);
}
}
It gets compiled to,
public class Test {
public Test();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: bipush 10
2: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
5: astore_1
6: bipush 30
8: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
11: astore_2
12: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
15: aload_1
16: invokevirtual #4 // Method java/lang/Integer.intValue:()I
19: aload_2
20: invokevirtual #4 // Method java/lang/Integer.intValue:()I
23: iadd
24: invokevirtual #5 // Method java/io/PrintStream.println:(I)V
27: return
}
Integer.valueOf
wraps the primitive types 10,30 to boxed types.- The addition
i+k
gets converted toi.intValue + k.intValue
. - The
intValue
method returns the primitive type assigned to that boxed type.
So the above operation has created four instances of the Integer.java class. This has to be done since Integer.java is truly an object at runtime unlike scala’s int which gets converted to primitive type at compile time.
This distinction is important and will be crucial in understanding scala collections.
We will discuss two advantages about this whole everything is an object representation.
Bigint example
Apart from the advantage that scala does not create objects for native types, it is also offers convenient syntax for other types such as Bigint.
In java, to add two BigInteger types, we need to use special method calls.
import java.math.BigInteger;
public class Test {
public static void main(String[] args) {
BigInteger a = new BigInteger("2000");
BigInteger b = new BigInteger("3000");
//Results in an error
System.out.println(a+b);
//Correct version
System.out.println(a.add(b));
}
}
Now this is fine for Big integers since we know that in java it is not a primitive type, but it is an additional tax imposed on developers.
In scala, since everything is an object and also the operators by themselves are just methods, adding two Big integers is pretty straightforward.
val x = BigInt("92839283928392839239829382938")
val y = BigInt("19020930293293209302932309032")
println(x+y)
We have to represent them as strings since they are too big even for long type.
This offers a convenient syntax and we can use the same operators we use for our known data types.
One could view this as a convenience feature for developers, but this it is much more. As we venture more into the world of scala, we will also encounter more advanced data types such Algebraic types which are useful in Machine learning/math related computations. What is more interesting is since everything is represented as an object we can now abstract certain things which makes it easier for programmers to write types of their own.
Typecasting
Since all of the types are now objects/classes and they all follow the tree hierarchy from the top type that is Any
, they can have some common methods
which are useful for all types.
We will explore some of the below.
val IntegerType = 20
val DoubleType = 20.0
val LongType : Long = 20
val FloatType : Float = 20.4f
println(IntegerType.toDouble)
println(DoubleType.toInt)
println(LongType.toShort)
println(FloatType.toDouble)
Each of the types will have type conversions to one or more other types. One need not worry about the current type under consideration since these methods are common and if there are any mistakes, they would be at compile time rather than run time.
In java, these kind of conversions is rather cumbersome.
public class Test {
public static void main(String[] args) {
int a = 20;
double d = 30.0;
long b = 30l;
float c = 20.0f;
//Primitive type conversions
System.out.println((double) a);
System.out.println((int) d);
System.out.println((short) b);
System.out.println((double) c);
//Boxed type conversions
System.out.println(Integer.valueOf(a).doubleValue());
System.out.println(Double.valueOf(d).intValue());
System.out.println(Long.valueOf(b).shortValue());
System.out.println(Float.valueOf(c).doubleValue());
}
}
This is another example of how if everything is an object is advantageous. One can even include a method such as toRoman
which converts the given value to a roman numeral. The programmer can choose which class to place this, an example would be Int.scala.
There are several more, but the point was to bring an intuition rather than an exhaustive listing.
Value comparison
In java you would have been taught not to use the ==
operator for comparing object types since they would compare references.
Scala has a different perspective to this. As we saw in case classes comparison can be done with ==
. This is
because everything is a value in scala.
With that said, since everything is also an object we can define our own method for the ==
comparison.
Just like the +
method above, the ==
is a synthetic function in the scala library.
/** Returns `true` if this value is equal to x, `false` otherwise. */
def ==(x: Byte): Boolean
/** Returns `true` if this value is equal to x, `false` otherwise. */
def ==(x: Short): Boolean
/** Returns `true` if this value is equal to x, `false` otherwise. */
def ==(x: Char): Boolean
/** Returns `true` if this value is equal to x, `false` otherwise. */
def ==(x: Int): Boolean
/** Returns `true` if this value is equal to x, `false` otherwise. */
def ==(x: Long): Boolean
/** Returns `true` if this value is equal to x, `false` otherwise. */
def ==(x: Float): Boolean
/** Returns `true` if this value is equal to x, `false` otherwise. */
def ==(x: Double): Boolean
Above code compiles to native java like comparison i.e for string it would be the equals
method which does a character by character comparison underneath.
Implementations in other languages and some notes
Turns out that scala is not the first language to implement “objects everywhere” concept.
In Ruby there are no primitive types and they operate almost similar to scala except for that fact that scala is object oriented in a very pure form, which means that everything is an object.
In later tutorials we will explore on how this concept enables us to write custom methods in fundamental types such as toRoman
for example and custom types such as Algebraic data types
Part 8 i.e the next part, I will explain about another cool feature of scala which is traits (more cooler than case classes ?) and how it really takes Object oriented programming to the next level.
Stay tuned and happy new year