Automatic Derivation
Flix supports automatic derivation of several traits, including:
Eq— to derive structural equality on the values of a type.Order— to derive a total ordering on the values of a type.ToString— to derive a human-readable string representation on the values of a type.Sendable— to enable the values of an (immutable) type to be sent over a channel.Coerce- to convert simple data types to their underlying representation.
Derivation of Eq and Order
We can automatically derive instances of the Eq and Order traits using
the with clause in the enum declaration. For example:
enum Shape with Eq, Order {
case Circle(Int32)
case Square(Int32)
case Rectangle(Int32, Int32)
}
The derived implementations are structural and rely on the order of the case declarations:
def main(): Unit \ IO =
println(Circle(123) == Circle(123)); // prints `true`.
println(Circle(123) != Square(123)); // prints `true`.
println(Circle(123) <= Circle(123)); // prints `true`.
println(Circle(456) <= Square(123)) // prints `true`.
Note: Automatic derivation of
EqandOrderrequires that the inner types of theenumimplementEqandOrderthemselves.
Derivation of ToString
We can also automatically derive ToString instances:
enum Shape with ToString {
case Circle(Int32)
case Square(Int32)
case Rectangle(Int32, Int32)
}
Then we can take advantage of string interpolation and write:
def main(): Unit \ IO =
let c = Circle(123);
let s = Square(123);
let r = Rectangle(123, 456);
println("A ${c}, ${s}, and ${r} walk into a bar.")
which prints:
A Circle(123), Square(123), and Rectangle(123, 456) walk into a bar.
Derivation of Sendable
We can automatically derive implementations of the Sendable trait (which
allow values of a specific type to be sent over a channel). For example:
enum Shape with Sendable, ToString {
case Circle(Int32)
}
def main(): Unit \ IO =
region rc {
let (tx, rx) = Channel.buffered(rc, 10);
Channel.send(Circle(123), tx); // OK, since Shape is Sendable.
println(Channel.recv(rx))
}
We cannot derive Sendable for types that rely on scoped mutable memory. For
example, if we try:
enum Shape[r: Region] with Sendable {
case Circle(Array[Int32, r])
}
The Flix compiler emits a compiler error:
❌ -- Safety Error --------------------------------------
>> Cannot derive 'Sendable' for type Shape[b27587945]
Because it takes a type parameter of kind 'Region'.
1 | enum Shape[r: Region] with Sendable {
^^^^^^^^
unable to derive Sendable.
This is because mutable data is not safe to share between threads.
Derivation of Coerce
We can automatically derive implementations of the Coerce trait.
The Coerce trait converts a simple (one-case) data type
to its underlying implementation.
enum Shape with Coerce {
case Circle(Int32)
}
def main(): Unit \ IO =
let c = Circle(123);
println("The radius is ${coerce(c)}")
We cannot derive Coerce for an enum with more than one case.
For example, if we try:
enum Shape with Coerce {
case Circle(Int32)
case Square(Int32)
}
The Flix compiler emits a compiler error:
❌ -- Derivation Error --------------------------------------------------
>> Cannot derive 'Coerce' for the non-singleton enum 'Shape'.
1 | enum Shape with Coerce {
^^^^^^
illegal derivation
'Coerce' can only be derived for enums with exactly one case.