Traits and fmt brought up to the compiler of 21 October

- Where an implementation is written: with its trait or its type, a
  newtype for anything else, and traits, types and impls usable above
  where they are written.
- One implementation that fits decides the type of a call.
- A function is written as #<procedure> wherever it is; Procedure.
- A (dyn T) implements the traits T requires.
- Formatting: the lambda that returns a vec of mixed pieces, in the
  table of errors; the short fmt tour in Language basics in the style of
  a string being a block.

🤖 Generated with [ECA](https://eca.dev)

Co-Authored-By: eca-agent <git@eca.dev>
This commit is contained in:
Linus Björnstam 2026-10-22 07:59:36 +02:00
parent 25720e4fb2
commit 5a9f65accf
2 changed files with 32 additions and 12 deletions

View file

@ -1983,19 +1983,17 @@ For output where the layout matters, @code{(std fmt)} has two layers. The simple
(println* ["Hello, " 42 ", ok? " #t]) ;; Hello, 42, ok? #t (println* ["Hello, " 42 ", ok? " #t]) ;; Hello, 42, ok? #t
}| }|
The other one is a small layout language. A layout is a @code{(Vec Block)}, where a block is a string, a number, a character or one of the combinators, and @code{show} prints it: The other one is a small layout language, made of blocks. A string, an int or a character is a block, so is a vec of blocks, and so is what each of the combinators gives back. @code{show} prints a block:
@codeblock[#:lang "bjolang"]|{ @codeblock[#:lang "bjolang"]|{
(show ["[" (padded/left 6 ["foo"]) "]" nl]) ;; [ foo] (show ["[" (padded/left 6 "foo") "]" nl]) ;; [ foo]
(show ["[" (padded/right 6 ["foo"]) "]" nl]) ;; [foo ] (show ["[" (padded/right 6 "foo") "]" nl]) ;; [foo ]
(show [(numeric/comma 1234567.891) nl]) ;; 1,234,567.891 (show [(numeric 1234567.891 #:comma 3) nl]) ;; 1,234,567.891
(show [(numeric 3.14159 #:precision 2) nl]) ;; 3.14 (show [(numeric 3.14159 #:precision 2) nl]) ;; 3.14
(render->string [(trimmed/right 5 ["abcdefgh"])]) ;; "abcde" (render->string (trimmed/right 5 "abcdefgh")) ;; "abcde"
(show [(joined/last (fun (s) (each [s])) (show [(joined/last BStr (fun (s) ["and " s])
(fun (s) (each ["and " s])) (list "a" "b" "c") ", ")
(list "a" "b" "c")
[", "])
nl]) ;; a, b, and c nl]) ;; a, b, and c
;; tabular takes columns, and sizes each to its content. ;; tabular takes columns, and sizes each to its content.
@ -2007,7 +2005,7 @@ The other one is a small layout language. A layout is a @code{(Vec Block)}, wher
;; bo 7 ;; bo 7
}| }|
The full set of combinators, paragraphs and settings is described in the reference for @code{(std fmt)}. The section on formatting text in "Writing programs" is a guide to the rest: paragraphs, tables, settings, and what to write when a layout does not compile. Every combinator is in the reference for @code{(std fmt)}.
@subsubsection{Regular expressions with (std rx)} @subsubsection{Regular expressions with (std rx)}
@ -2174,6 +2172,8 @@ The compiler picks the one whose arguments match. Two rules keep that choice una
@item{The type has to be known where the call is. @code{(describe (list))} is an error, because an empty list could be either, and so is a call at @code{(List %a)} in a generic function: neither implementation is written for every element type. Give such a value an annotation, or write one implementation for @code{(List %a)} with a @code{where}, as in the next section.} @item{The type has to be known where the call is. @code{(describe (list))} is an error, because an empty list could be either, and so is a call at @code{(List %a)} in a generic function: neither implementation is written for every element type. Give such a value an annotation, or write one implementation for @code{(List %a)} with a @code{where}, as in the next section.}
} }
Where only one implementation fits, it decides the type instead. With @code{(impl (Describe (List int)) ...)} and no other implementation for a list, @code{(describe (list))} is a call at @code{(List int)}, and a function with @code{(where (Describe %a))} called with @code{(list)} is too.
@subsubsection{Conditional and blanket implementations} @subsubsection{Conditional and blanket implementations}
An implementation for a generic type can require something of its type variables, with a @code{where} after the head. A list can be described if its elements can: An implementation for a generic type can require something of its type variables, with a @code{where} after the head. A list can be described if its elements can:
@ -2187,7 +2187,7 @@ An implementation for a generic type can require something of its type variables
(describe (list Clubs Hearts)) ;; "clubs, hearts" (describe (list Clubs Hearts)) ;; "clubs, hearts"
}| }|
A blanket implementation is one for a bare type variable. It applies to every type that has no implementation of its own, and a more specific implementation always wins over it. A type whose head has implementations only for other arguments, such as a @code{(List bool)} beside the @code{(List int)} above, is an error rather than the blanket. Only the module that declares a trait may write its blanket: A blanket implementation is one for a bare type variable. It applies to every type that has no implementation of its own, and a more specific implementation always wins over it. A type whose head has implementations only for other arguments, such as a @code{(List bool)} beside the @code{(List int)} above, is an error rather than the blanket. Only the module that declares a trait may write its blanket, since a blanket is for every type and so for types the module did not declare:
@codeblock[#:lang "bjolang"]|{ @codeblock[#:lang "bjolang"]|{
(def/trait (Weight %a) (def/trait (Weight %a)
@ -2203,6 +2203,21 @@ A blanket implementation is one for a bare type variable. It applies to every ty
(weight "hello") ;; 5 (weight "hello") ;; 5
}| }|
@subsubsection{Where an implementation is written}
An implementation is written in the module that declares its trait, or in the one that declares its type. Every module that can name both the trait and the type then sees the implementation, so a call at a type means the same in every module. A module may implement its own traits for any type, and any trait for its own types:
@codeblock[#:lang "bjolang"]|{
(impl (Describe (List int)) ...) ;; fine: Describe is declared here
(impl (Displayed Card) ...) ;; fine: Card is declared here
(impl (Displayed (Tuple int string)) ;; refused: neither is declared here
...)
}|
A tuple, a type of the runtime such as @code{List}, and a .NET type are declared by no module, so only the module of a trait may implement it for one of them. To give such a type a trait from somewhere else, wrap it in a newtype of your own, as described below, and implement the trait for that. @code{#:via} gives the newtype the implementations the wrapped type already has.
Traits, types and implementations may be used above where they are written. A function may name a type that is declared further down, and call a method of a trait that is declared below it, and an implementation may stand above its trait.
@subsubsection{Traits that require other traits} @subsubsection{Traits that require other traits}
A trait may say that every type implementing it implements another trait too, with a @code{where} in the trait itself. Something that greets must have a name: A trait may say that every type implementing it implements another trait too, with a @code{where} in the trait itself. Something that greets must have a name:
@ -2272,7 +2287,7 @@ Four traits from the prelude are worth knowing by heart, since the rest of the l
(hash-combine (eq-hash (record-ref c rank)) (eq-hash (record-ref c suit))))) (hash-combine (eq-hash (record-ref c rank)) (eq-hash (record-ref c suit)))))
}| }|
The compiler writes a @code{Written} for every type you declare, so a value prints as you would build it: @code{(Card (rank Queen) (suit Hearts))}, @code{(Pip 7)}, @code{Hearts}. A generic type is written when its type arguments are. An @code{#:opaque} type is written as @code{#<Name>}, and a field that holds a function as @code{#<procedure>}. @code{Displayed} has a blanket implementation that displays a value as it is written. The compiler writes a @code{Written} for every type you declare, so a value prints as you would build it: @code{(Card (rank Queen) (suit Hearts))}, @code{(Pip 7)}, @code{Hearts}. A generic type is written when its type arguments are. An @code{#:opaque} type is written as @code{#<Name>}, and a function as @code{#<procedure>}, wherever it is: @code{(println (Some (fun (x) (+ x 1))))} prints @code{(Some #<procedure>)}, and so does a field that holds one, or a function at the REPL's prompt. Every function type has the head @code{Procedure}, so a trait of your own can have an implementation for functions, @code{(impl (Describe Procedure) ...)}, which applies to all of them. A blanket does not reach them, which is why @code{(= f g)} on two functions is still refused. @code{Displayed} has a blanket implementation that displays a value as it is written.
To print a type your own way, implement @code{Displayed} for the text a person reads, as the card game did, or @code{Written} to change the written form too. The method to give is @code{display-into} (or @code{write-into}), which adds the text to a string builder; @code{displayed} and @code{written} then come with the trait: To print a type your own way, implement @code{Displayed} for the text a person reads, as the card game did, or @code{Written} to change the written form too. The method to give is @code{display-into} (or @code{write-into}), which adds the text to a string builder; @code{displayed} and @code{written} then come with the trait:
@ -2305,6 +2320,8 @@ A list holds values of one type. When you need a collection of different types t
;; '("1" "two" "Q♥") ;; '("1" "two" "Q♥")
}| }|
A @code{(dyn T)} implements the traits that @code{T} requires as well, and the ones those require, through the value it holds. With @code{Greet} and @code{Named} from above, a @code{(dyn Greet)} can be given to @code{introduce}, which calls @code{name-of}, and to any function that asks for @code{(where (Named %a))}. So a trait cannot be packed if a trait it requires cannot be packed, or has associated types.
Packing is always explicit, and a @code{dyn} cannot be turned back into the value it came from. It is the exception rather than the rule: most code is better served by a union. Packing is always explicit, and a @code{dyn} cannot be turned back into the value it came from. It is the exception rather than the rule: most code is better served by a union.
@subsubsection{Traits that are .NET interfaces} @subsubsection{Traits that are .NET interfaces}

View file

@ -932,8 +932,11 @@ The messages for the common mistakes, and what to write instead:
| Block: ... found a boolean, or found a bool | @code{(if b "yes" "no")} | | Block: ... found a boolean, or found a bool | @code{(if b "yes" "no")} |
| Block: ... found a (Vec string) | @code{(vec-map BStr xs)}, or @code{(: xs Block)} above @code{(def xs ...)} | | Block: ... found a (Vec string) | @code{(vec-map BStr xs)}, or @code{(: xs Block)} above @code{(def xs ...)} |
| Block: ... found a (Vec (Vec ...)), at a lambda that returns a vec | @code{(each [...])} around the vec the lambda returns | | Block: ... found a (Vec (Vec ...)), at a lambda that returns a vec | @code{(each [...])} around the vec the lambda returns |
| these types do not match, naming two of int, string and char, at a lambda that returns a vec | @code{(each [...])} around the vec the lambda returns |
} }
The last two are one mistake, a lambda given to a function that does not say it returns a block. Its vec is a vec of strings if all its pieces are strings, and then @code{show} gets a vec of vecs. If the pieces are of different kinds, such as a name and a count, the vec has no type at all, and the compiler says so inside the lambda.
@subsubsection{A block of your own} @subsubsection{A block of your own}
@code{captured} renders content to a string and gives it to a function, which returns the block to write in its place. It is how a block that needs the whole of its content is made, and how padding and trimming are written: @code{captured} renders content to a string and gives it to a function, which returns the block to write in its place. It is how a block that needs the whole of its content is made, and how padding and trimming are written: