Understanding what 'type' keyword does in Scala

Solution 1:

Actually the type keyword in Scala can do much more than just aliasing a complicated type to a shorter name. It introduces type members.

As you know, a class can have field members and method members. Well, Scala also allows a class to have type members.

In your particular case type is, indeed, introducing an alias that allows you to write more concise code. The type system just replaces the alias with the actual type when type-checking is performed.

But you can also have something like this

trait Base {
  type T

  def method: T
}

class Implementation extends Base {
  type T = Int

  def method: T = 42
}

Like any other member of a class, type members can also be abstract (you just don't specify what their value actually is) and can be overridden in implementations.

Type members can be viewed as dual of generics since much of the things you can implement with generics can be translated into abstract type members.

So yes, they can be used for aliasing, but don't limit them to just this, since they are a powerful feature of Scala's type system.

Please see this excellent answer for more details:

Scala: Abstract types vs generics

Solution 2:

Yes, the type alias FunctorType is just a shorthand for

(LocalDate, HolidayCalendar, Int, Boolean) => LocalDate

Type aliases are often used to keep the rest of the code simple: you can now write

def doSomeThing(f: FunctorType)

which will be interpreted by the compiler as

def doSomeThing(f: (LocalDate, HolidayCalendar, Int, Boolean) => LocalDate)

This helps to avoid defining many custom types that are just tuples or functions defined on other types, for example.

There are also several other interesting use cases for type, as described for example in this chapter of Programming in Scala.

Solution 3:

I liked the answer from Roland Ewald since he described with a very simple use case of type alias, and for more detail introduced a very nice tutorial. However, since another use case is introduced in this post named type members, I would like to mention the most practical use case of it, which I liked very much: (this part is taken from here:)

Abstract Type:

type T

T above says that this type that is going to be used, is unknown yet, and depending on the concrete subclass, it will be defined. The best way always for understanding the programming concepts is providing an example: Suppose you have the following scenario:

Without Type Abstraction

Here you will get compilation error, because eat method in classes Cow and Tiger do not override the eat method in class Animal, because their parameter types are different. It's Grass in class Cow, and Meat in class Tiger vs. Food in class Animal which is super class and all subclasses must conform.

Now back to type abstraction, by the following diagram and simply adding a type abstraction, you can define the type of the input, in according subclass itself.

With Abstract Type

Now look at following codes:

  val cow1: Cow = new Cow
  val cow2: Cow = new Cow

  cow1 eat new cow1.SuitableFood
  cow2 eat new cow1.SuitableFood

  val tiger: Tiger = new Tiger
  cow1 eat new tiger.SuitableFood // Compiler error

Compiler is happy and we improve our design. We can feed our cow with cow.SuitableFood and compiler prevent us with feeding out cow with the food which is suitable for Tiger. But what if we want to make difference between the type of cow1 SuitableFood and cow2 SuitabeFood. In another word, it would be very handy in some scenarios if the path by which we reach to the type (of course via object) does basically matter. Thanks to the advanced features in scala, it is possible:

Path-dependent types: Scala objects can have types as members. The meaning of the type, depends on the path you use to access it. The path is determined by the reference to an object (aka an instance of a class). In order to implement this scenario, you need to define class Grass inside the Cow, i.e., Cow is the outer class and Grass is the inner class. The structure will be like this:

  class Cow extends Animal {
    class Grass extends Food
    type SuitableFood = Grass
    override def eat(food: this.SuitableFood): Unit = {}
  }

  class Tiger extends Animal {
    class Meat extends Food
    type SuitableFood = Meat
    override def eat(food: this.SuitableFood): Unit = {}
  }

Now if you try to compile this code:

  1. val cow1: Cow = new Cow
  2. val cow2: Cow = new Cow

  3. cow1 eat new cow1.SuitableFood
  4. cow2 eat new cow1.SuitableFood // compilation error

On line 4 you will see an error because Grass is now an inner class of Cow, therefore, to create an instance of Grass, we need a cow object and this cow object determines the path. So 2 cow objects give rise to 2 different path. In this scenario, cow2 only wants to eat food especially created for it. So:

cow2 eat new cow2.SuitableFood

Now everybody is happy :-)

Solution 4:

Just an example to see how to use "type" as alias :

type Action = () => Unit

The definition above defines Action to be an alias of the type of procedures(methodes) that take an empty parameter list and that return Unit.