-
Notifications
You must be signed in to change notification settings - Fork 0
trait
A trait names what a value can do: Rust calls it a trait, Haskell a typeclass, Swift a protocol, Java, Go and
TypeScript an interface, JavaScript builds it from prototypes. Wasp writes trait; interface, protocol,
typeclass, prototype, capability, aspect and feature mean the same and are hinted toward trait.
As struct puts it: interfaces are just classes without concrete fields and without implementations.
| trait | operation | who conforms |
|---|---|---|
| Comparable | compare(a:T, b:T) |
numbers by value, texts and characters by code points |
| Equatable | equals(a:T, b:T) |
every value, an instance field by field |
sort, min, max and < need Comparable values, == Equatable ones.
sort ["b" "a"] // ["a" "b"]
3 is Comparable // true
[1 2] is Comparable // false
A class conforms by defining the operation for itself, no declaration needed (structural conformance, also retroactively for a type written elsewhere):
class person{name age:int}
compare(a:person, b:person) := a.age - b.age // negative, 0 or positive
people = [person("Ann" 40) person("Bob" 30)]
youngest = (sort people)#1 // Bob
person("Ann" 40) > person("Bob" 30) // true
Equality is field by field unless the type defines equals:
class word{spelling}
equals(a:word, b:word) := a.spelling.lower == b.spelling.lower
word("Hi") == word("hi") // true
Using a value that does not conform is a compile error naming the trait and the definition to write:
class dot{x:int}
sort [dot(2) dot(1)]
// dot is not Comparable: sort needs compare(a:dot, b:dot)
// fix: define compare(a:dot, b:dot) := … (negative, 0 or positive)
A class may claim a trait; the claim is checked:
class dot{x:int} is Comparable // error until compare(a:dot, b:dot) is defined
trait shape{area perimeter}
class square{side:int}
area(s:square) := s.side*s.side
perimeter(s:square) := 4*s.side
area(square(3)) // 9
square(1) is shape // true
Each type defines its own area; a call picks the one of its argument's type.
Where the type of a value is known at compile time the call goes straight to the type's operation. Where it is not (the items of a list sorted at run time), Comparable dispatches through a witness table in the module. Implementation notes: notes/traits.md in the warp repository.
A parameter can ask for a list of values that conform to a trait:
smallest(xs: Comparable list) := (sort xs)#1
smallest([age(3) age(1)]) // fine: compare(a:age, b:age) exists
smallest([dot(3) dot(1)]) // compile error: dot is not Comparable: … needs compare(a:dot, b:dot)
A trait may give an operation a default body: trait shape{area; describe(s) := area(s) * 2}.
A type gives its own text by defining text for itself (P31): as text, str, text, print and an interpolation
hole all call it; every other value keeps its own text.
class person{name:text}
text(p:person) := "P " + p.name
x = person{name:"a"}
"is \(x)"
A type defining iterate for itself is walked by for x in b and searched by x in b, through the list it gives:
class bag{a:int b:int}
iterate(g:bag) := [g.a, g.b]
s = 0
for x in bag(3, 4) { s += x }
s