Builtin Traits
In Nostos, all types automatically have implementations for the core traits Hash, Eq, Show, and Copy. No special syntax is needed - these capabilities are always available for every type you define.
Available Builtin Traits
Every type in Nostos automatically supports these four traits:
| Trait | Function | Description |
|---|---|---|
| Hash | hash(x) -> Int | Hash function for use in maps/sets |
| Show | show(x) -> String | String representation for debugging |
| Eq | ==, != operators | Equality comparison |
| Copy | copy(x) -> T | Deep copy of values |
Basic Usage
Just define your types - all traits are available automatically.
# All types automatically have Hash, Show, Eq, Copy
type Point = Point(Int, Int)
type Color = Red | Green | Blue
type Person = { name: String, age: Int }
main() = {
p = Point(3, 4)
# Using Show
println(show(p)) # Point(3, 4)
# Using Eq
println(show(p == Point(3, 4))) # true
# Using Hash
println(show(hash(p))) # some integer
# Using Copy
p2 = copy(p)
println(show(p == p2)) # true
}
Variant Types
All variant (sum) types have builtin traits - from simple enums to complex variants with fields.
# Simple enum-like variants
type Color = Red | Green | Blue
# Variants with fields
type Result = Ok(Int) | Err(String)
# Complex variants
type Expression = Number(Int) | Variable(String) | Add(Expression, Expression)
main() = {
# Using Show
println(show(Red)) # "Red"
println(show(Ok(42))) # "Ok(42)"
# Using Eq
println(show(Red == Red)) # true
println(show(Red == Blue)) # false
}
Record Types
Records with named fields also have all builtin traits. The implementations use all fields.
type Person = { name: String, age: Int }
main() = {
alice = Person("Alice", 30)
bob = Person("Bob", 25)
alice2 = Person("Alice", 30)
# Show displays all fields
println(show(alice)) # Person{name: Alice, age: 30}
# Eq compares all fields
println(show(alice == alice2)) # true
println(show(alice == bob)) # false
# Copy creates an independent copy
copied = copy(alice)
println(show(copied == alice)) # true
}
Nested Types
When a type contains another type, the inner type's trait methods are called automatically. This means composition works naturally.
# Inner types
type Email = Email(String)
type Address = { street: String, city: String, zip: Int }
# Outer type that uses them
type Contact = { name: String, email: Email, address: Address }
main() = {
email = Email("alice@example.com")
addr = Address("123 Main St", "Springfield", 12345)
contact = Contact("Alice", email, addr)
# Show recursively shows nested types
println(show(contact))
# Eq recursively compares nested types
contact2 = Contact("Alice", Email("alice@example.com"), Address("123 Main St", "Springfield", 12345))
println(show(contact == contact2)) # true
}
Hash Consistency
The builtin Hash trait ensures that equal values always produce equal hash codes - a requirement for correct behavior in maps and sets.
type UserId = UserId(Int)
main() = {
u1 = UserId(42)
u2 = UserId(42)
u3 = UserId(99)
# Equal values have equal hashes
println(show(hash(u1) == hash(u2))) # true
# Different values typically have different hashes
println(show(hash(u1) == hash(u3))) # false (usually)
# This makes UserId usable as a map key
}
Practical Example: Custom Map Key
Here's a practical example using builtin traits to create a type suitable for use as a map key.
type UserId = UserId(Int)
type UserData = { name: String, score: Int }
# Simple association list lookup using builtin Eq
lookup([], key) = "not found"
lookup([(k, v) | rest], key) = if k == key then v else lookup(rest, key)
main() = {
users = [(UserId(1), UserData("Alice", 100)),
(UserId(2), UserData("Bob", 85)),
(UserId(3), UserData("Charlie", 92))]
# lookup uses the builtin == from Eq
result = lookup(users, UserId(2))
println(show(result)) # UserData{name: Bob, score: 85}
}