Traits
Traits describe what values of different types can do. For example, something
as mundane as the +
operator is described by the Add
type as an implicit
argument. Any type a
that has an Add[a]
implementation can be used with the
+
operator. Thus, a trait is simply a special name for an implicit argument
that describes an ability of another value.
Traits are not a language provided feature, as in there is no trait
keyword.
Traits are simply the name that is given to the concept of expressing abilities
through implicits.
Using traits
The +
opertor can be implemented for any type via the Add
trait.
import std/ops for Add
type Inches[Float]
provide Add(add: fn(left, right) { Inches(left.0 + right.0) })
// The + uses Add definition above for inches.
assert_eq(Inches(3.0) + Inches(4.0), Inches(7.0))
This works because +
desugars to a function call equivalent to the following
add
function.
// Similar to std/ops::add.
def add(x: a, y: a)(using adder: Add[a]) -> a {
adder.add(x, y)
}
Since an implicit may itself use implicits, it is possible for a traits to put constraints on the values it expects.
type Pair[a, b](a, b)
provide (using Default[a], Default[b]) -> Default[Pair[a, b]] {
Default(default: fn() Pair(default(), default()))
}
// The default_pair is automatically inserted into the default()
// function call, and the default_pair will pull in Default
// traits for Int and String to compute their defaults.
let pair: Pair[Int, String] = default()
assert_eq(pair, Pair(0, ""))
Creating new traits
A trait is just a value that provides proof that another value can be used
in such a way. For example, instances of Add[a]
trait are values that
contain a function with some definition of addition for a type a
.
type Named[a](name: fn(a) -> String)
def name(x: a)(using named: Named[a]) -> String {
named.name(x)
}
In the following example, a custom Named
type describes the ability for
something to have a name that can be accessed in a generic way via the name
function.
type Person(name: String, age: Int)
provide Named[Person] = Named(name: fn(person) { person.name })
type File(path: String, read_only: Bool)
provide Named[File] = Named(name: fn(file) { file.name })
assert_eq(name(Person(name: "Izumi", age: 35)), "Izumi")
assert_eq(name(File(path: "/etc/file.txt", read_only: True)), "/etc/file.txt")