5

Type Parameterization and Type System: Introduction

 2 years ago
source link: https://blog.knoldus.com/type-parameterization-and-type-system-introduction/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Type Parameterization and Type System: Introduction

Reading Time: 3 minutes

Scala is a static type language, which means types are checked at compile time instead of runtime. Type System provides safety during the compile time because we are detecting the error beforehand as these can be a potential error at runtime. Type parameterization also called as generics combined with Scala’s type system.

We can write generic code so that we can reuse that code. Suppose, we want to add two decimal numbers. We’d make something like this:

object Addition extends App {

  def sumFloat(number1:Float,number2:Float): Float = number1 + number2
  sumFloat(3.2,2.3)
  
}

Further, requirement comes to add all value types, then we’d make something like this:

object Addition extends App {

  def sumAnyVal(number1:AnyVal,number2:AnyVal): AnyVal =
    (number1, number2) match {
      case (number1: Int, number2: Int) => number1 + number2
      case (number1: Float, number2: Float) => number1 + number2
      case (number1: Double, number2: Double) => number1 + number2
      case _ => throw RuntimeException
    }
  sumAnyVal(1,2)
  sumAnyVal(3.2,2.3)
}

Further, requirement comes to add String as well i.e., concatenate the String inputs. Now the condition becomes to add decimal values, all value types and Strings as well. Then, we’d do something like this:

object Addition extends App {

  def sumAny(number1:Any,number2:Any): Any =
    (number1, number2) match {
      case (number1: Int, number2: Int) => number1 + number2
      case (number1: Float, number2: Float) => number1 + number2
      case (number1: Double, number2: Double) => number1 + number2
      case(number1: String, number2: String) => number1 + number2
      case _ => throw new RuntimeException
    }
  sumAny(1,2)
  sumAny(3.2,2.3)
  sumAny("Hello","World")
}

Let’s say we call sumAny method as:

sumAny(new AnyRef, new AnyRef)

This will give the result as RuntimeException and that’s where the problem arises.

Exception in thread "main" java.lang.RuntimeException

To overcome this problem, type safety comes as a solution as it will tell at the compile time only that sumAny(new AnyRef, new AnyRef) will be a potential error, kindly handle it first.

object Addition extends App {

  trait Adder[T] {
    def sum(number1: T, number2: T): T
  }

  def sum[T](number1: T, number2: T)(implicit adder: Adder[T]): T =
    adder.sum(number1, number2)

  implicit val addString = new Adder[String] {
    override def sum(word1: String, word2: String): String = word1 + word2
  }
  sum("Hello", "World")
} 

Now if we call sum method as sum(new AnyRef, new AnyRef), it will result in an error at compile time only.

Compile time error will come up as

For more on type system, refer to Scala Documentation or The Scala Type System.

Type Parameterization

Type parameterization also called as generics combined with Scala’s type system. It provides the ability to write code which is more predictable during compile time rather than time.

It allows us to write generic classes and traits.
We can also make parameters and return type of method as generic. This leads to question where to make classes or traits generic and where does make parameters and return type generic?

If we declare the type parameter at class level, we assign the actual type upon construction and can’t change it later- all invocation of the methods present inside the generic class, will have to use the same type.

scala> trait Animal
defined trait Animal

scala> case class Dog() extends Animal
defined class Dog

scala> case class Cat() extends Animal
defined class Cat

scala> class Owner[A<: Animal]{
     | def feed(a:A) = println("Feeding my pet")
     | }
defined class Owner

Now, let’s create Cat and Dog object and make a call to feed method.

scala> val dog = Dog()
dog: Dog = Dog()

scala> val cat = Cat()
cat: Cat = Cat()

scala> val owner = new Owner[Dog]
owner: Owner[Dog] = Owner@4cf4d528

scala> owner.feed(dog)
Feeding my pet

Here, a single owner has a specific pet, so it is good to use type at class level.

If we use type parameter at the method level, we can assign a different type in each invocation of the method.

scala> class AnimalLover {
     | def feed[A<: Animal](a:A) = println("Feeding street animals")
     | }
defined class AnimalLover
scala> val dog = Dog()
dog: Dog = Dog()

scala> val cat = Cat()
cat: Cat = Cat()

scala> val animalLoverPerson = new Animal
Animal   AnimalLover

scala> val animalLoverPerson = new AnimalLover
animalLoverPerson: AnimalLover = AnimalLover@657c8ad9

scala> animalLoverPerson.feed(dog)
Feeding street animals

scala> animalLoverPerson.feed(cat)
Feeding street animals

An animal lover person might feed both Dogs and Cats, so feed method must be parameterized, and in this use case there is no use in adding a parameter at class level.

Conclusion

Generics are a powerful way of abstraction and writing type safe code which is more compile time reliant rather than run time.

This is small introduction to Type System and Type Parameterization. Some of the advanced topics variance, bounds, higherkinded type will be discussed in the next write up.

References

Scala Tutorial : Type System
Scala Type Parameters


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK