Printing: written and displayed
->str gives way to two traits, as Scheme's write and display: a value as the reader reads it, and the value for a person. Every type of your own is written in constructor syntax. The examples, the REPL session and the expect output follow. 🤖 Generated with [ECA](https://eca.dev) Co-Authored-By: eca-agent <git@eca.dev>
This commit is contained in:
parent
481538a1cb
commit
e448ee0d93
2 changed files with 107 additions and 49 deletions
|
|
@ -104,9 +104,11 @@ Then there are some special cases, like string interpolation:
|
|||
(a-string "hej"))
|
||||
(println $"This is interpolated: ${an-int} ${a-string}")
|
||||
;; Which is literally the same as:
|
||||
(println (str "This is interpolated: " (->str an-int) " " (->str a-string))))
|
||||
(println (str "This is interpolated: " (displayed an-int) " " (displayed a-string))))
|
||||
}
|
||||
|
||||
Each hole goes through @code{displayed}, so a string hole gives its text without quotes. See @em{Converting to and from strings} below.
|
||||
|
||||
@subsubsection{Function types}
|
||||
|
||||
A function takes any number of arguments and produces a value. The signature of a function in bjolang is denoted using the arrow @code{->}:
|
||||
|
|
@ -456,7 +458,7 @@ A few things to know about unions:
|
|||
@read-list{
|
||||
- Case names share a namespace with everything else in the module, so two unions in the same module cannot both have a case called @code{Red}.
|
||||
- A union case cannot have a @code{#:mutable} field.
|
||||
- @code{println} on a union value without a @code{->str} implementation prints the generated C# name, something like @code{Card { rank = Queen { }, suit = Hearts { } }}. Implement @code{->str} (see the traits section) for anything you intend to print.
|
||||
- @code{println} on a value of your own type prints it as you would build it, for example @code{(Card (rank Queen) (suit Hearts))} or @code{(Pip 7)}. The compiler writes this for each type you declare. To print a type another way, implement @code{Displayed} (see the traits section).
|
||||
}
|
||||
|
||||
@subsubsection{Types that refer to themselves and each other}
|
||||
|
|
@ -543,23 +545,31 @@ The rest of this chapter uses a card game as a running example, so here are its
|
|||
(: tricks int))))
|
||||
|
||||
;; How a card prints. Traits are explained later; for now it is enough
|
||||
;; to know that this is what println and #"...${...}" use.
|
||||
(impl (->str Suit)
|
||||
(defun (->str s)
|
||||
(match s (Clubs "♣") (Diamonds "♦") (Hearts "♥") (Spades "♠"))))
|
||||
;; to know that Displayed is what println and #"...${...}" use, and that
|
||||
;; display-into adds the text to the string builder b.
|
||||
(impl (Displayed Suit)
|
||||
(defun (display-into s b)
|
||||
(stringbuilder-add-string! b
|
||||
(match s (Clubs "♣") (Diamonds "♦") (Hearts "♥") (Spades "♠")))))
|
||||
|
||||
(impl (->str Rank)
|
||||
(defun (->str r)
|
||||
(match r
|
||||
((Pip n) (int->string n))
|
||||
(Jack "J") (Queen "Q") (King "K") (Ace "A"))))
|
||||
(impl (Displayed Rank)
|
||||
(defun (display-into r b)
|
||||
(stringbuilder-add-string! b
|
||||
(match r
|
||||
((Pip n) (int->string n))
|
||||
(Jack "J") (Queen "Q") (King "K") (Ace "A")))))
|
||||
|
||||
(impl (->str Card)
|
||||
(defun (->str c) #"${(record-ref c rank)}${(record-ref c suit)}"))
|
||||
(impl (Displayed Card)
|
||||
(defun (display-into c b)
|
||||
(display-into (record-ref c rank) b)
|
||||
(display-into (record-ref c suit) b)))
|
||||
|
||||
(println (Card (rank Queen) (suit Hearts))) ;; Q♥
|
||||
(written (Card (rank Queen) (suit Hearts))) ;; "(Card (rank Queen) (suit Hearts))"
|
||||
}|
|
||||
|
||||
Without the three implementations, @code{println} prints the card as it is written: @code{(Card (rank Queen) (suit Hearts))}.
|
||||
|
||||
@code{Card} is a struct because it is two small values and is copied around a lot. @code{Player} is a record because it holds a whole hand. That is the usual rule of thumb: a struct for something small that behaves like a value, a record for everything else. A struct also cannot contain itself and cannot have mutable fields.
|
||||
|
||||
Records are updated by copying, with @code{record-set}:
|
||||
|
|
@ -1926,12 +1936,34 @@ Do not build strings by writing to a string port. Writing to a port is I/O as fa
|
|||
|
||||
@subsubsection{Converting to and from strings}
|
||||
|
||||
@code{->str} turns anything into a string, and @code{println} and interpolation use it. It is a trait, so your own types can decide what they look like (the card game did that for @code{Card}). For the basic types there are also named conversions:
|
||||
Two functions turn any value into a string:
|
||||
|
||||
@read-list{
|
||||
- @code{written} gives the value as the reader reads it, as Scheme's @code{write} does. A string is in quotes, a char is @code{#\a}, a number keeps the suffix of its type (@code{21L}), and a double always has a point (@code{3.0}). The REPL shows a result this way.
|
||||
- @code{displayed} gives the value for a person to read, as Scheme's @code{display} does. A string is its text, and a number has no suffix. @code{println}, @code{print} and interpolation use it.
|
||||
}
|
||||
|
||||
@codeblock[#:lang "bjolang"]|{
|
||||
(written "hej") ;; "\"hej\""
|
||||
(displayed "hej") ;; "hej"
|
||||
(written 21L) ;; "21L"
|
||||
(displayed 21L) ;; "21"
|
||||
(written (list "a" "b")) ;; "(\"a\" \"b\")"
|
||||
(displayed (list "a" "b")) ;; "(a b)"
|
||||
(written (Some (Tuple 1 "x"))) ;; "(Some (Tuple 1 \"x\"))"
|
||||
(println (Fruit (name "banana") (amount 5) (ripeness Rotten)))
|
||||
;; prints (Fruit (name "banana") (amount 5) (ripeness Rotten))
|
||||
}|
|
||||
|
||||
Every type you declare is written as you would build it. A type that prints differently for a person implements @code{Displayed}, as the card game did for @code{Card}; one that is written differently implements @code{Written}. The traits section has more. @code{->str} is deprecated: use @code{displayed}.
|
||||
|
||||
For the basic types there are also named conversions:
|
||||
|
||||
@codeblock[#:lang "bjolang"]|{
|
||||
(int->string 42) ;; "42"
|
||||
(double->string 2.5) ;; "2.5"
|
||||
(->str 3.0) ;; "3"
|
||||
(displayed 3.0) ;; "3"
|
||||
(written 3.0) ;; "3.0"
|
||||
(string->int "42") ;; 42, and throws on anything that is not a number
|
||||
(string->double "1.5") ;; 1.5
|
||||
(char->string #\a) ;; "a"
|
||||
|
|
@ -2018,7 +2050,7 @@ Define patterns at the top level, as above, rather than writing @code{#rx(...)}
|
|||
|
||||
@subsection{Traits}
|
||||
|
||||
A trait is a set of functions that a type can implement. It is how bjolang does what other languages do with interfaces, type classes or overloading: @code{=} is a trait method, so is @code{->str}, and so are @code{length}, @code{fold} and @code{compare}.
|
||||
A trait is a set of functions that a type can implement. It is how bjolang does what other languages do with interfaces, type classes or overloading: @code{=} is a trait method, so is @code{displayed}, and so are @code{length}, @code{fold} and @code{compare}.
|
||||
|
||||
@subsubsection{Declaring and implementing a trait}
|
||||
|
||||
|
|
@ -2089,7 +2121,7 @@ A generic function that calls a trait method has to say that its type variable i
|
|||
|
||||
@code{describe-all} can now be called with a vec of anything that has a @code{Describe}, and calling it with anything else is a compile error at the call.
|
||||
|
||||
For @code{Eq} and @code{->str} you may leave the @code{where} out: the compiler adds them by itself when a function uses @code{=} or @code{->str} on a type variable.
|
||||
For @code{Eq}, @code{Written} and @code{Displayed} you may leave the @code{where} out: the compiler adds them by itself when a function uses @code{=}, @code{written} or @code{displayed} on a type variable.
|
||||
|
||||
@subsubsection{Associated types}
|
||||
|
||||
|
|
@ -2189,7 +2221,7 @@ A trait may say that every type implementing it implements another trait too, wi
|
|||
|
||||
@code{introduce} asks only for @code{Greet}, and may still call @code{name-of}: a type that can greet is known to have a name. An implementation of @code{Greet} for a type with no @code{Named} is a compile error at the implementation.
|
||||
|
||||
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}.
|
||||
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}. @code{Displayed} requires @code{Written} in the same way.
|
||||
|
||||
@subsubsection{Giving a newtype the traits of the type it wraps}
|
||||
|
||||
|
|
@ -2216,16 +2248,17 @@ A newtype is the type it wraps when the program runs, so it can borrow that type
|
|||
|
||||
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}
|
||||
@subsubsection{Eq, Ord, Written and Displayed}
|
||||
|
||||
Three traits from the prelude are worth knowing by heart, since the rest of the library leans on them.
|
||||
Four traits from the prelude are worth knowing by heart, since the rest of the library leans on them.
|
||||
|
||||
@read-table{
|
||||
| Trait | Methods | Gives you |
|
||||
|----------------+---------------------------------+------------------------------------------------------------------|
|
||||
| @code{Eq} | @code{=}, @code{eq-hash} | equality, and use as a key in maps and sets |
|
||||
| @code{Ord} | @code{compare} | @code{less?}, @code{least}, @code{list-sort}, @code{list-max}, ordered collections |
|
||||
| @code{->str} | @code{->str} | @code{println}, @code{print}, string interpolation |
|
||||
| Trait | Methods | Gives you |
|
||||
|-------------------+-----------------------------------------+------------------------------------------------------------------|
|
||||
| @code{Eq} | @code{=}, @code{eq-hash} | equality, and use as a key in maps and sets |
|
||||
| @code{Ord} | @code{compare} | @code{less?}, @code{least}, @code{list-sort}, @code{list-max}, ordered collections |
|
||||
| @code{Written} | @code{write-into}, @code{written} | the REPL's output, the failure text of @code{expect} |
|
||||
| @code{Displayed} | @code{display-into}, @code{displayed} | @code{println}, @code{print}, string interpolation |
|
||||
}
|
||||
|
||||
@code{Eq} and @code{Ord} can be derived with @code{type/derive}, as shown earlier. When you write them by hand, they must be written in the module that declares the type, because they are compiled into the type itself: they become its .NET @code{Equals}, @code{GetHashCode} and @code{CompareTo}, which is why a .NET dictionary or a sorted collection agrees with @code{=} and @code{compare}. @code{=} and @code{eq-hash} must agree: two values that are equal must have the same hash. @code{compare} returns a negative number, zero or a positive number.
|
||||
|
|
@ -2239,19 +2272,36 @@ Three traits from the prelude are worth knowing by heart, since the rest of the
|
|||
(hash-combine (eq-hash (record-ref c rank)) (eq-hash (record-ref c suit)))))
|
||||
}|
|
||||
|
||||
@code{->str} has a blanket implementation that uses .NET's @code{ToString}, which is why printing one of your own types shows its C# name until you implement it.
|
||||
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.
|
||||
|
||||
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:
|
||||
|
||||
@codeblock[#:lang "bjolang"]|{
|
||||
(type (: Money (Union (: Kr int))))
|
||||
|
||||
(impl (Displayed Money)
|
||||
(defun (display-into m b)
|
||||
(match m ((Kr n) (stringbuilder-add-string! b #"${n} kr")))))
|
||||
|
||||
(println (Kr 42)) ;; 42 kr
|
||||
(written (Kr 42)) ;; "(Kr 42)"
|
||||
}|
|
||||
|
||||
A container that holds other values calls @code{write-into} or @code{display-into} on them with the same builder. @code{elements-print-into} and @code{entries-print-into} from the prelude lay out the elements of a collection, or the entries of a map, for you.
|
||||
|
||||
@code{->str} is deprecated. It still works, through @code{displayed}, but each use gives a warning.
|
||||
|
||||
@subsubsection{Values of different types in one collection: dyn}
|
||||
|
||||
A list holds values of one type. When you need a collection of different types that all implement the same trait, pack each value as a @code{(dyn Trait)}:
|
||||
|
||||
@codeblock[#:lang "bjolang"]|{
|
||||
(: render-all (-> (List (dyn ->str)) (List string)))
|
||||
(defun (render-all xs) (list-map ->str xs))
|
||||
(: render-all (-> (List (dyn Displayed)) (List string)))
|
||||
(defun (render-all xs) (list-map displayed xs))
|
||||
|
||||
(render-all (list (dyn ->str 1)
|
||||
(dyn ->str "two")
|
||||
(dyn ->str (Card (rank Queen) (suit Hearts)))))
|
||||
(render-all (list (dyn Displayed 1)
|
||||
(dyn Displayed "two")
|
||||
(dyn Displayed (Card (rank Queen) (suit Hearts)))))
|
||||
;; '("1" "two" "Q♥")
|
||||
}|
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue