From bd71102c19b6d08ec634a75994d2afe8bd6921470d3ca3765994d7f346b64943 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20Bj=C3=B6rnstam?= Date: Wed, 21 Oct 2026 08:25:02 +0200 Subject: [PATCH] Newtypes, and giving one the traits of the type it wraps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- _01.Language-basics.sz | 44 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/_01.Language-basics.sz b/_01.Language-basics.sz index 87d6a59..8bd22cb 100644 --- a/_01.Language-basics.sz +++ b/_01.Language-basics.sz @@ -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.