1. Introduction

Scala is a statically typed programming language. This means the compiler determines the type of a variable at compile time.

Type declaration is a Scala feature that enables us to declare our own types.

In this short tutorial, we’ll learn how to do type declaration in Scala using the type keyword. First, we’ll learn to use it as a type alias. Then, we’ll learn to declare an abstract type member and implement it.

2. Type Alias

A type alias is usually used to simplify declaration for complex types, such as parameterized types or function types.

Let’s explore examples of those aliases and also look at some illegal implementations of type aliases.

2.1. Type Alias for Parameterized Types

Let’s define a shortcut for a list of integers:

object ListIntFunctions {
  type IntItems = List[Int]
}

Here, we defined IntItems as a type alias for List[Int]. This way, we can use IntItems in the place of List[Int], making it a little more readable, and providing a central definition of how we implement that.

Let’s use IntItems in a method inside the object:

def mean(items: IntItems): Double = {
  items.sum.toDouble / items.length
}

We can see that we’re able to use sum and length – methods of List – on items.

If we want to use mean outside the ListIntFunctions object, we can also use List[Int] for the parameter:

val intList = List(3, 6, 2, 2)
assert(ListIntFunctions.mean(intList) === 3.25)

We can also use another type alias for List[int] if we define it:

type SomeInts = List[Int]
val intList: SomeInts = List(3, 6, 2, 2)
assert(ListIntFunctions.mean(intList) === 3.25)

In other words, we can use List[Int] or its aliases interchangeably.

2.2. Type Alias for Function Types

Another use case for type alias is function types. We can use a type alias to simplify the declaration of a function type to later use it in other methods.

For example, we can create a type alias for any function that has an Int param and returns a String:

type IntToString = Int => String

Then, we can use the type alias in a method:

def IntItemsToString(items: IntItems, intToString: IntToString): List[String] = {
  items.map(intToString)
}

To use that, first, let’s declare an IntItems and IntToString to use later in the method:

val intList = (1 to 3).toList
def getChicken(item: Int): String = {
  s"$item chicken"
}

Then, we can apply IntItemsToString with intList and getChicken:

val stringList = ListIntFunctions.IntItemsToString(intList, getChicken)
assert(stringList === List("1 chicken", "2 chicken", "3 chicken"))

As we can see, all of the members of the intList become chickens by the getChicken function.

2.3. Illegal Type Alias

There are some cases that we need to avoid when declaring a type alias.

First, it’s illegal to create a type alias with a reference to itself:

scala> type A = List[A]
<console>:11: error: illegal cyclic reference involving type A

Next, we cannot create a type alias to a type that has required parameters without defining them:

scala> type T = List
<console>:11: error: type List takes type parameters

Finally, we are unable to select a part of another type that has more than one element, like Tuple:

scala> type Y = Tuple2[Int, String]
defined type alias Y

scala> type Z = List[Y.key]
<console>:11: error: not found: value Y

3. Type Member

A type member can be declared in an object, class, or trait to be used within the scope. The most common application is the abstract type members, which we can declare in a trait and assign to any type in the implementation class.

3.1. Abstract Type Member

As an example, let’s say we need to create objects that perform repetition on a value or item. These objects would be implemented from a trait so they all have the same contract.

Let’s create the Repeat trait for this purpose:

trait Repeat {
  type RepeatType
  def apply(item: RepeatType, reps: Int): RepeatType
}

We’ve defined RepeatType as a type that needs to be defined in classes that extend Repeat. Also, we have an apply method to be implemented depending on the type we’re assigning.

Now, let’s see how to implement the abstract type member.

3.2. Implementing an Abstract Type Member

We can implement our abstract type in a class that implements the trait. Let’s implement the Repeat trait for Int type:

object IntegerRepeat extends Repeat {
  type RepeatType = Int
  def apply(item: RepeatType, reps: Int): RepeatType = {
    (item.toString * reps).toInt
  }
}

Now, let’s see what IntegerRepeat does:

assert(IntegerRepeat(3, 5) == 33333)
assert(IntegerRepeat(10, 3) == 101010)

As we can see, it creates a new integer by using the reps parameter as the number of times to repeat the item.

Let’s implement another object that repeats a List:

object ListRepeat extends Repeat {
  type RepeatType = List[Any]
  def apply(item: RepeatType, reps: Int): RepeatType = {
    (1 to reps).map(_ => item).reduce(_ ::: _)
  }
}

Even though ListRepeat extends the same trait as IntegerRepeat, it performs a completely different sort of operation.

Let’s see what ListRepeat does with a List:

assert(ListRepeat(List("a", "b"), 2) == Seq("a", "b", "a", "b"))
assert(ListRepeat(List(1), 3) == Seq(1, 1, 1))

4. Conclusion

In this article, we explored the type keyword in Scala.

We saw how it allows us to create a type alias for simplifying complex types and function types. Then, we saw how to create an abstract type member and implement it.

As usual, the example code from this article is available over on GitHub.

Comments are closed on this article!