Scala Tutorials Part #7 - Objects Everywhere


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

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.

Objects everywhere

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.

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

}

Abstract class error

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.

Martin Odersky - Objects everywhere

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.

Java illegal plus

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
}

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


Tagged Under


Scala


Search this website...




Keeping up with blogs...

I blog occasionally and mostly write about Software engineering and the likes. If you are interested in keeping up with new blog posts, you should follow me on twitter where I usually tweet when I publish them. You can also use the RSS feed , or even subscribe via email below.

Feedio Subscribe


Share & Like

If you like this post, then you can either share/discuss/vote up on the below sites.



Thoughts ...

Please feel free to share your comments below for discussion

Blog comments powered by Disqus