The Traits module provides type-based polymorphism for Kit. Traits define
shared behavior that types can implement, enabling generic programming
and consistent interfaces across different types.
Trait definitions — declare the methods a type must implement
Trait implementations — provide the actual methods for specific types
Built-in types like Int, Float, String, and Bool
come with pre-implemented trait methods that you can use directly via the
TypeName.method pattern.
Defining Traits
Define a trait using the trait keyword with a name and type parameter:
# Trait with one required methodtraitEq a
eq?: (a, a) -> Bool
Traits declare required methods with a type signature (e.g., eq?: (a, a) -> Bool).
Types implementing the trait must provide implementations for all required methods.
Supertraits
A trait can require other traits using the requires keyword:
# Ord requires Eq - any type implementing Ord must also implement EqtraitOrd a requires Eq
compare: (a, a) -> Ordering
Implementing Traits
Implement a trait for a type using extend with with:
# Implement Eq for IntextendIntwith Eq
eq? = fn(a, b) => a == b
# Implement Ord for Int (requires Eq already implemented)extendIntwith Ord
compare = fn(a, b) =>
if a < b then Less
else if a > b then Greater
else Equal
Conditional Implementations
For parameterized types, use where to require trait bounds on type parameters:
# A list is Eq if its elements are EqextendList a with Eq where Eq a
eq? = fn(xs, ys) =>
match (xs, ys)
| ([], []) -> true
| ([x | xt], [y | yt]) -> eq? x y && eq? xt yt
| _ -> false
Standard Traits
The Traits module defines these core traits:
Eq
Equality comparison
Types that can be compared for equality.
Method
Type
Description
eq?
(a, a) -> Bool
Required: equality test
Ord
Ordering comparison (requires Eq)
Types that have a total ordering. The Ordering type is
Less | Equal | Greater.
Method
Type
Description
compare
(a, a) -> Ordering
Required: three-way comparison
For boolean comparisons, use pattern matching on the result: match Int.compare a b | Less -> true | _ -> false
Show
String representation
Types that can be converted to a string representation.
Types that have well-defined minimum and maximum values. Useful for
fixed-width integers, floating point types, and bounded numeric types.
Method
Type
Description
min-bound
a
Minimum value of the type
max-bound
a
Maximum value of the type
Clone
Explicit copying of values
Provides explicit copying semantics. While Kit values are generally immutable
and copying is implicit, Clone is useful when you want to explicitly indicate
that a copy is being made.
Method
Type
Description
clone
a -> a
Create a copy of the value
# For primitive types, clone is identity# For compound types, clone creates a deep copy
original = [1, 2, 3]
copy = clone original
Hash
Hashable types (requires Eq)
Types that can compute hash values. Types implementing Hash can be used
as keys in Maps and elements in Sets. The hash function must satisfy:
if eq?(a, b) then hash(a) == hash(b).
Method
Type
Description
hash
a -> Int
Compute hash value
# Implement Hash for a custom typeextendMyTypewith Hash
hash = fn(x) => hash(x.id)
# Then use in Map/Set
my-map = Map.empty : Map MyType String
Debug
Debug representation with type information
Similar to Show but includes type information for debugging purposes.
While Show provides human-readable output, Debug provides output useful
for development and debugging.
Provides a common interface for error types. Enables consistent error handling
across packages while allowing domain-specific error types with pattern matching.
Provides a uniform interface for working with both eager and lazy values.
For eager values, force is the identity function.
For lazy values, force evaluates the thunk.
Method
Type
Description
force
a -> a
Force evaluation of a value
# Works with both eager and lazy values
compute = fn(x) => Int.force(x) * 2
compute(5) # => 10
compute(lazy(fn => 5)) # => 10 (after forcing)
Serialization Traits
These traits provide standard interfaces for encoding and decoding values
to various formats. Each format package defines its own marker type.
StringEncode
Text-based serialization
Encodes values to string representations. Use for text-based formats
like JSON, YAML, TOML, CSV, etc.
Decodes values from string representations. Returns a Result to handle
parsing errors.
Method
Type
Description
decode
String -> Result a error
Decode string to value
extendUserwith StringDecode Json JsonError
decode = fn(s) =>
# parsing logic...
Ok {name: "...", age: 0# Usage
Json.decode(str) : Result User JsonError
BinaryEncode
Binary serialization
Encodes values to binary representations (byte arrays). Use for binary formats
like MessagePack, Protocol Buffers, CBOR, BSON, Avro, etc.
Method
Type
Description
encode-bytes
a -> List Int
Encode value to bytes
BinaryDecode
Binary deserialization
Decodes values from binary representations. Returns a Result to handle
parsing errors.
Method
Type
Description
decode-bytes
List Int -> Result a error
Decode bytes to value
Standard Types
The Traits module also defines several useful algebraic data types.
Ordering
type Ordering = Less | Equal | Greater
The result type for three-way comparisons. Used by the Ord trait's
compare method.
match Int.compare a b
| Less -> println"a < b"
| Equal -> println"a == b"
| Greater -> println"a > b"
Ordering Helper Functions
Function
Type
Description
Ordering.is-less?
Ordering -> Bool
Returns true if Less
Ordering.is-equal?
Ordering -> Bool
Returns true if Equal
Ordering.is-greater?
Ordering -> Bool
Returns true if Greater
Ordering.reverse
Ordering -> Ordering
Reverses the ordering (Less ↔ Greater)
Ordering.then
Ordering -> Ordering -> Ordering
Chains orderings (if first is Equal, use second)
# Ordering predicatesprintln (Ordering.is-less? Less) # => trueprintln (Ordering.is-equal? Equal) # => true# Reverse for descending sortprintln (Ordering.reverse Less) # => Greater# Chain comparisons (compare by x first, then by y)
compare-points = fn(p1, p2) =>
Ordering.then
(Int.compare p1.x p2.x)
(Int.compare p1.y p2.y)
Either
type Either a b = Left a | Right b
Represents a value of one of two possible types. Left typically
represents the "first" or "error" case, while Right typically
represents the "second" or "success" case.
# Either can represent alternative types
parse-id : String -> Either String Int
parse-id = fn(s) =>
match Int.parse s
| Some n -> Right n
| None -> Left s # Keep as string if not a numbermatch parse-id "123"
| Left s -> println"String ID: ${s}"
| Right n -> println"Numeric ID: ${n}"
Either implements Eq, Show, Debug, and Clone when its type parameters do.
Patch
type Patch a = Unchanged | Assign a | Clear
A three-state type for representing partial updates. Distinguishes between
"not provided" (Unchanged), "set to value" (Assign),
and "set to null" (Clear).
Constructor
Meaning
Unchanged
Field not provided — preserve existing value
Assign a
Explicitly set to a new value
Clear
Explicitly set to null/empty
Useful for REST API PATCH semantics, configuration merging, and any scenario
where you need to distinguish "not provided" from "set to null".
Note: We use Assign instead of Set to avoid
conflict with Kit's Set collection type.
Patch Helper Functions
Function
Type
Description
Patch.unchanged?
Patch a -> Bool
Returns true if Unchanged
Patch.has-value?
Patch a -> Bool
Returns true if Assign _
Patch.clear?
Patch a -> Bool
Returns true if Clear
Patch.unwrap-or
a -> Patch a -> a
Returns value if Assign, otherwise the default
Patch.from-option
Option a -> Patch a
Converts None to Unchanged, Some x to Assign x
Patch.apply
Option a -> Patch a -> Option a
Apply patch to current value
Patch Example
import Traits
# Define a patch type for updating userstypeUserPatch = {
name: PatchString,
email: PatchString,
age: PatchInt
}
# Apply patch to an existing userapply-user-patch = fn(user, patch) =>
name = Patch.apply (Some user.name) patch.name |> Option.unwrap-or user.name
email = Patch.apply (Some user.email) patch.email |> Option.unwrap-or user.email
age = Patch.apply (Some user.age) patch.age |> Option.unwrap-or user.age
{name: name, email: email, age: age}
# Example: Update only the emailuser = {name: "Alice", email: "old@example.com", age: 30}
patch = {name: Unchanged, email: Assign"new@example.com", age: Unchangedupdated = apply-user-patch user patch
# => {name: "Alice", email: "new@example.com", age: 30}
Patch implements Eq, Ord, Show, Debug, Clone, Default, and Hash when its type parameter does.
Utility Functions
The Traits module provides these utility functions that work with the Ord trait:
Function
Type
Description
min
a -> a -> a (where Ord a)
Returns the smaller of two values
max
a -> a -> a (where Ord a)
Returns the larger of two values
clamp
a -> a -> a -> a (where Ord a)
Restricts a value to a range
import Traits
# min and maxprintln (min 37) # => 3println (max 37) # => 7# clamp restricts a value to [low, high]println (clamp 5110) # => 5 (within range)println (clamp -5110) # => 1 (below range)println (clamp 15110) # => 10 (above range)
Built-in Type Methods
Kit's built-in types have pre-implemented trait methods available via
the TypeName.method? pattern. Boolean-returning methods
use the ? suffix convention.
See Protocols in the Language Guide for more details.
Note: Automatic trait dispatch for generic functions is not yet
implemented. For now, use type-qualified methods directly (e.g., Int.compare a b)
rather than a generic compare a b.