trait - pannous/warp GitHub Wiki
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, ability, 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 |
|---|---|---|
| Equatable | equals(a:T, b:T) |
every value, an instance field by field |
| Orderable | less(a:T, b:T) |
numbers, texts, characters, ordered records |
| Comparable | compare(a:T, b:T) |
Equatable + Orderable # Sortable follows |
| Stringable | string(a:T) |
every value with a textual representation TODO |
| Formattable | format(a:T, f:F) |
numbers, dates, structured values |
| Printable | print(a:T) |
every value with a textual representation |
| Serializable | serialize(a:T) |
values with a defined external representation (as string?) |
| Hashable | hash(a:T) |
immutable values with stable equality |
| Cloneable | clone(a:T) |
values that can be duplicated |
| Iterable | iterate(a:T) |
collections, ranges, iterator objects |
| Sized | size(a:T) |
collections and other finite containers |
| Iterable | iterate(a:T) |
sequences, collections, ranges, iterator objects |
| Indexable | get(a:T, i:I) |
arrays, lists, texts, maps |
| Addable | add(a:T, b:T) |
numbers; texts and collections by concatenation |
| Callable | call(f:T, args…) |
functions, closures, callable objects |
| Parsable | parse(s:Text):T |
values constructible from text |
| Approximable | circa(a:T,b:T, criterion=default) |
values with some notion of similarity ≈ ~ |
sort, min, max and < need Comparable values, == Equatable ones.
trait Comparable { requires compare or equals and less # Default implementations can/must be overwritten compare(a, b) → {-1,0,+1} = equals(a,b) ? 0 : less(a,b) ? -1 : 1 equals(a, b) = compare(a,b) == 0 less(a, b) = compare(a,b) < 0 }
compare(a,b) → {-1,0,+1} equals(a,b) := compare(a,b) == 0 less(a,b) := compare(a,b) < 0 compare(a,b) → a == b ? 0 : a > b ? 1 : -1 # => We just need either of these definitions!
sort ["b" "a"] // ["a" "b"]
3 is Comparable // yes
[1 2] is Comparable // no
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) // yes
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") // yes
Using a value that does not conform is a compile error naming the trait and the definition to write:
# fails on purpose: the error it shows
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 // yes
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([person("Ann", 3) person("Bo", 1)]) // fine: compare(a:person, b:person) 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