Polymorphism

Calcit models polymorphism with traits. Traits define method capabilities and can be attached to struct/enum definitions with impl-traits.

For capability-based dispatch via struct/enum-attached impls (used by records/tuples created from them), see Traits.

Historically, the idea was inspired by JavaScript, and also borrowed from a trick of Haskell (simulating OOP with immutable data structures). The current model is trait-based.

Key terms

  • Trait: A named capability with method signatures (defined by deftrait).
  • Trait impl: An impl record providing method implementations for a trait.
  • impl-traits: Attaches one or more trait impl records to a struct/enum definition.
  • assert-traits: Adds a compile-time hint and performs a runtime check that a value satisfies a trait.

Define a trait

deftrait Show
  :show (:: :fn ('T) ('T) :string)

deftrait Eq
  :eq? (:: :fn ('T) ('T 'T) :bool)

Traits are values and can be referenced like normal symbols.

Implement a trait for a struct/enum definition

deftrait MyFoo
  :foo (:: :fn ('T) ('T) :string)

defimpl MyFooImpl MyFoo
  :foo $ fn (p) (str "|foo " (:name p))

let
    Person0 $ defstruct Person (:name :string)
    Person $ impl-traits Person0 MyFooImpl
    p $ %{} Person (:name |Alice)
  println $ .foo p

impl-traits returns a new struct/enum definition with trait implementations attached. You can also attach multiple traits at once:

let
    Person0 $ defstruct Person (:name :string)
    Person $ impl-traits Person0 ShowImpl EqImpl MyFooImpl
    p $ %{} Person (:name |Alice)
  println $ .show p
  println $ .foo p

Trait checks and type hints

assert-traits marks a local as having a trait and validates it at runtime:

let
    p $ %{} Person (:name |Alice)
  assert-traits p MyFoo
  .foo p

If the trait is missing or required methods are not implemented, assert-traits raises an error.

Built-in traits

Core types provide built-in trait implementations (e.g. Show, Eq, Compare, Add, Len, Mappable). These are registered by the runtime, so values like numbers, strings, lists, maps, and records already satisfy common traits.

Notes

  • There is no inheritance. Behavior sharing is done via traits and impl-traits.
  • Method calls resolve through attached trait impls first, then built-in implementations.
  • Use assert-traits when a function relies on trait methods and you want early, clear failures.

Further reading

  • Dev log(中文) https://github.com/calcit-lang/calcit/discussions/44
  • Dev log in video(中文) https://www.bilibili.com/video/BV1Ky4y137cv