Traits and instances
A trait defines an open set of types that support a common set of functions. For example, here's a trait for shapes with a common function to compute the area:
trait T: Shape {
area(shape: T): Float
}
The function becomes a top level function area[T: Shape](shape: T): Float
.
Here T
is a bounded type parameter: It can only be instantiated to a type that has an instance of the Shape
trait.
Consider these two types that would be good candidates for having an instance of the Shape
trait:
data Circle(radius: Float)
data Rectangle(width: Float, height: Float)
Only types defined with the data
or newtype
keyword can have trait instances.
Instances for these types can be created with the instance
keyword as follows:
instance Circle: Shape {
area(shape: Circle): Float {
Float.pi() * (shape.radius ^ 2.0)
}
}
instance Rectangle: Shape {
area(shape: Rectangle): Float {
shape.width * shape.height
}
}
Each instance provides an implementation of area
specific to the type.
Here's an example of how to define a normal top level function with a bounded type parameter:
printArea[T: Shape](shape: T) {
Log.trace("Area: " + area(shape))
}
printArea(Circle(0.0)) // Prints "Area: 0"
printArea(Rectangle(5.0, 6.0)) // Prints "Area: 30"
Multiple bounds are separated by colons, e.g. foo[T: Bar: Baz]
means that T
must have instances for both Bar
and Baz
.
Traits with type parameters
Traits can have type parameters:
trait I: Rpc[O] {}
instance MyMessage: Rpc[Int] {}
The choice of I
fully determines O
- in this case, if I
is MyMessage
, then O
is Int
.
Automatic traits
If instances for the following traits are not explicitly defined, they will be generated automatically.
This only applies to types defined with the data
or newtype
keyword.
// Used for == !=
trait T: Equal {
equals(x: T, y: T): Bool
}
// Used for < > <= >= and sorting
trait T: Order {
compare(x: T, y: T): Ordering
}
// Used to display values for debugging
trait T: Show {
show(value: T): String
}
// Used for binary serialization
trait T: Serializable {
serializeUsing(serialization: Serialization, value: T): Unit
deserializeUsing(serialization: Serialization): T
}
// Used for throwing and catching exceptions
trait T: HasAnyTag {
anyTag(): AnyTag[T]
}