Types
Scala's type system is very powerful and allow for zero cost abstraction.
Type alias
Scala uses the keyword type
to work with types. In particular you can use this to create custom types and type alias:
type Cow // creates a type "Cow"
type CowHerd = List[Cow] // creates a type "CowHerd" which is an alias for a List of Cow
Function type aliases
Scala uses the type =>[A, B]
(can be written A => B
) for a function that take an A
and returns a B
. You can also create type aliases for function types.
type IntToString = Int => String
Upper type bounds
Scala uses the syntax <:
to specify upper bound relation between types.
NB: type Herd[_]
is known as a higher-kinded type.
type Animal // simple type
type Cow <: Animal // type Cow is a subtype of type Animal
type Herd[A <: Animal] = List[A] // type Herd[A] takes a type A that must be a subtype of Animal and is an alias for a List of A
type CowHerd = Herd[Cow] // type CowHerd is a Herd of Cow
Lower type bounds
Scala uses the syntax >:
to specify lower bound relation between types.
trait List[+A]:
def prepend[B >: A](elem: B): NonEmptyList[B] = NonEmptyList(elem, this)
case class NonEmptyList[+A](head: A, tail: List[A]) extends List[A]
object Nil extends List[Nothing]
Check the full example here.
Abstract type members
A type member can be declared in an object, class, or trait to be used within the scope.
type Food
type Meat <: Food
type Grass <: Food
trait Animal:
type SuitableFood <: Food
def eat(some: SuitableFood) = ???
class Lion extends Animal:
type SuitableFood = Meat
class Cow extends Animal:
type SuitableFood = Grass
val lion = Lion()
val cow = Cow()
val meat: Meat = ???
val grass: Grass = ???
lion.eat(meat) // OK
cow.eat(grass) // OK
// lion.eat(grass) // doesn't compile
// ^^^^^ required lion.SuitableFood
Abstract type members vs Generics
Pretty much everything achievable through abstract type members is achievable through generics (and vice-versa) so why bother with both ?
Here are good ressources:
Opaque types
Consider the following situation.
type UserID = Long
type VehicleID = Long
def findVehicle(vehicleID: VehicleID) = ???
val userID: UserID = ???
findVehicle(userID) // OK but WRONG (using a UserID instead of VehicleID)
This example compiles fine because the types UserID
and VehicleID
are just aliases for Long
which means we can uses one in place of the other (correct but unwanted behavious).
Scala uses the keyword opaque
as a type qualifier.
object Users:
opaque type UserID = Long
object Vehicles:
opaque type VehicleID = Long
import Users.UserID
import Vehicles.VehicleID
def findUser(userID: UserID) = ???
def findVehicle(vehicleID: VehicleID) = ???
val userID: UserID = ???
// findVehicle(userID) // doesn't compile
// ^^^^^^ required VehicleID
Union types
Scala has a built-in union type |[A, B]
(can be written A | B
) that expresses that a value can be either of type A
or B
.
val IntOrString: Int | String = if true then 1 else "one"
Intersection types
Scala has a built-in intersection type &[A, B]
(can be written A & B
) that expresses that a value is both of type A
and B
.
trait Resettable:
def reset(): Unit
trait Growable[A]:
def add(a: A): Unit
def f(x: Resettable & Growable[String]): Unit =
x.reset()
x.add("first")