Newtypes, and giving one the traits of the type it wraps

A newtype beside aliases in "Your own types", and #:via and type/derive
for one in the traits chapter.

🤖 Generated with [ECA](https://eca.dev) (anthropic/claude-opus-5-5)

Co-Authored-By: eca-agent <git@eca.dev>
This commit is contained in:
Linus Björnstam 2026-10-21 08:25:02 +02:00
parent 6d9c9bf8a0
commit bd71102c19

View file

@ -354,6 +354,25 @@ A type alias is a different name for a type that already exists. It is declared
(type (: Stringalias string))
}
@subsubsection{Newtypes}
A newtype is the opposite of an alias: a type of its own, which the compiler will not mix up with the type it wraps, but which is that type when the program runs, so it costs nothing.
@codeblock[#:lang "bjolang"]|{
(type (: UserId (Newtype int)))
(type (: OrderId (Newtype int)))
(def u (UserId 7)) ;; built by its name
(def (UserId n) u) ;; taken apart by a pattern: n is 7
(: (-> UserId string))
(defun (lookup-user id) ...)
(lookup-user (OrderId 7)) ;; a compile error: an OrderId is not a UserId
}|
A newtype does not get the traits of the type it wraps: @code{(+ u u)} is an error, though @code{(+ 7 7)} is not. It gets those you give it, as the section on traits shows.
@subsubsection{Records and structs}
Records and structs are direct mappings to c# records and immutable structs. The constructor is the type name, together with parenthesised fields. As follows.
@ -2172,6 +2191,31 @@ A trait may say that every type implementing it implements another trait too, wi
The prelude's @code{Ord} requires @code{Eq} this way, so a function with @code{(where (Ord %a))} can use @code{=} as well as @code{compare}.
@subsubsection{Giving a newtype the traits of the type it wraps}
A newtype is the type it wraps when the program runs, so it can borrow that type's implementation of a trait as it is, with @code{#:via}:
@codeblock[#:lang "bjolang"]|{
(type (: Meters (Newtype double)))
(impl (Eq Meters) #:via double)
(impl (Ord Meters) #:via double)
(impl (Num Meters) #:via double) ;; + - * /
(impl (Ordered Meters) #:via double) ;; < > <= >=
(+ (Meters 1.5) (Meters 2.0)) ;; (Meters 3.5)
(+ (Meters 1.5) 2) ;; a number literal is a Meters too
}|
@code{type/derive} writes these for you, for any trait, when the type is a newtype:
@codeblock[#:lang "bjolang"]|{
(type/derive (Eq Ord Num Ordered)
(: Meters (Newtype double)))
}|
A trait that should behave differently is written by hand instead. That is the other use of a newtype: the same data with a different behaviour, such as an @code{Eq} that ignores case on a @code{(Newtype string)}. Sets and maps of the newtype use that implementation, not the wrapped type's.
@subsubsection{Eq, Ord and ->str}
Three traits from the prelude are worth knowing by heart, since the rest of the library leans on them.