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"))
|
(a-string "hej"))
|
||||||
(println $"This is interpolated: ${an-int} ${a-string}")
|
(println $"This is interpolated: ${an-int} ${a-string}")
|
||||||
;; Which is literally the same as:
|
;; 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}
|
@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{->}:
|
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{
|
@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}.
|
- 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.
|
- 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}
|
@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))))
|
(: tricks int))))
|
||||||
|
|
||||||
;; How a card prints. Traits are explained later; for now it is enough
|
;; How a card prints. Traits are explained later; for now it is enough
|
||||||
;; to know that this is what println and #"...${...}" use.
|
;; to know that Displayed is what println and #"...${...}" use, and that
|
||||||
(impl (->str Suit)
|
;; display-into adds the text to the string builder b.
|
||||||
(defun (->str s)
|
(impl (Displayed Suit)
|
||||||
(match s (Clubs "♣") (Diamonds "♦") (Hearts "♥") (Spades "♠"))))
|
(defun (display-into s b)
|
||||||
|
(stringbuilder-add-string! b
|
||||||
|
(match s (Clubs "♣") (Diamonds "♦") (Hearts "♥") (Spades "♠")))))
|
||||||
|
|
||||||
(impl (->str Rank)
|
(impl (Displayed Rank)
|
||||||
(defun (->str r)
|
(defun (display-into r b)
|
||||||
(match r
|
(stringbuilder-add-string! b
|
||||||
((Pip n) (int->string n))
|
(match r
|
||||||
(Jack "J") (Queen "Q") (King "K") (Ace "A"))))
|
((Pip n) (int->string n))
|
||||||
|
(Jack "J") (Queen "Q") (King "K") (Ace "A")))))
|
||||||
|
|
||||||
(impl (->str Card)
|
(impl (Displayed Card)
|
||||||
(defun (->str c) #"${(record-ref c rank)}${(record-ref c suit)}"))
|
(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♥
|
(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.
|
@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}:
|
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}
|
@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"]|{
|
@codeblock[#:lang "bjolang"]|{
|
||||||
(int->string 42) ;; "42"
|
(int->string 42) ;; "42"
|
||||||
(double->string 2.5) ;; "2.5"
|
(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->int "42") ;; 42, and throws on anything that is not a number
|
||||||
(string->double "1.5") ;; 1.5
|
(string->double "1.5") ;; 1.5
|
||||||
(char->string #\a) ;; "a"
|
(char->string #\a) ;; "a"
|
||||||
|
|
@ -2018,7 +2050,7 @@ Define patterns at the top level, as above, rather than writing @code{#rx(...)}
|
||||||
|
|
||||||
@subsection{Traits}
|
@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}
|
@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.
|
@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}
|
@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.
|
@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}
|
@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.
|
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{
|
@read-table{
|
||||||
| Trait | Methods | Gives you |
|
| Trait | Methods | Gives you |
|
||||||
|----------------+---------------------------------+------------------------------------------------------------------|
|
|-------------------+-----------------------------------------+------------------------------------------------------------------|
|
||||||
| @code{Eq} | @code{=}, @code{eq-hash} | equality, and use as a key in maps and sets |
|
| @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{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 |
|
| @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.
|
@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)))))
|
(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}
|
@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)}:
|
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"]|{
|
@codeblock[#:lang "bjolang"]|{
|
||||||
(: render-all (-> (List (dyn ->str)) (List string)))
|
(: render-all (-> (List (dyn Displayed)) (List string)))
|
||||||
(defun (render-all xs) (list-map ->str xs))
|
(defun (render-all xs) (list-map displayed xs))
|
||||||
|
|
||||||
(render-all (list (dyn ->str 1)
|
(render-all (list (dyn Displayed 1)
|
||||||
(dyn ->str "two")
|
(dyn Displayed "two")
|
||||||
(dyn ->str (Card (rank Queen) (suit Hearts)))))
|
(dyn Displayed (Card (rank Queen) (suit Hearts)))))
|
||||||
;; '("1" "two" "Q♥")
|
;; '("1" "two" "Q♥")
|
||||||
}|
|
}|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,18 +22,22 @@ Here is the deck from the first part as a module of its own. Put it in @code{dec
|
||||||
(: Rank (Union (: Pip int) Jack Queen King Ace))
|
(: Rank (Union (: Pip int) Jack Queen King Ace))
|
||||||
(: Card (Struct (: rank Rank) (: suit Suit))))
|
(: Card (Struct (: rank Rank) (: suit Suit))))
|
||||||
|
|
||||||
(impl (->str Suit)
|
(impl (Displayed Suit)
|
||||||
(defun (->str s)
|
(defun (display-into s b)
|
||||||
(match s (Clubs "♣") (Diamonds "♦") (Hearts "♥") (Spades "♠"))))
|
(stringbuilder-add-string! b
|
||||||
|
(match s (Clubs "♣") (Diamonds "♦") (Hearts "♥") (Spades "♠")))))
|
||||||
|
|
||||||
(impl (->str Rank)
|
(impl (Displayed Rank)
|
||||||
(defun (->str r)
|
(defun (display-into r b)
|
||||||
(match r
|
(stringbuilder-add-string! b
|
||||||
((Pip n) (int->string n))
|
(match r
|
||||||
(Jack "J") (Queen "Q") (King "K") (Ace "A"))))
|
((Pip n) (int->string n))
|
||||||
|
(Jack "J") (Queen "Q") (King "K") (Ace "A")))))
|
||||||
|
|
||||||
(impl (->str Card)
|
(impl (Displayed Card)
|
||||||
(defun (->str c) #"${(record-ref c rank)}${(record-ref c suit)}"))
|
(defun (display-into c b)
|
||||||
|
(display-into (record-ref c rank) b)
|
||||||
|
(display-into (record-ref c suit) b)))
|
||||||
|
|
||||||
;; Not exported: only this module needs them.
|
;; Not exported: only this module needs them.
|
||||||
(def all-suits [Clubs Diamonds Hearts Spades])
|
(def all-suits [Clubs Diamonds Hearts Spades])
|
||||||
|
|
@ -78,7 +82,7 @@ Notice how the imports and exports interact here:
|
||||||
@read-list{
|
@read-list{
|
||||||
- Relative imports: @code{(import "deck.bjo")} specifies a file path relative to the importing file. Bjolang compiles @code{deck.bjo} into @code{deck.dll} ahead of time, automatically rebuilding it whenever it (or any of its dependencies) changes.
|
- Relative imports: @code{(import "deck.bjo")} specifies a file path relative to the importing file. Bjolang compiles @code{deck.bjo} into @code{deck.dll} ahead of time, automatically rebuilding it whenever it (or any of its dependencies) changes.
|
||||||
- Type exports are complete: Exporting a type exposes all its constructors, union cases, and fields. Because @code{Card} is exported, @code{game.bjo} can freely construct @code{(Card (rank Ace) (suit Spades))} and pattern-match on its fields.
|
- Type exports are complete: Exporting a type exposes all its constructors, union cases, and fields. Because @code{Card} is exported, @code{game.bjo} can freely construct @code{(Card (rank Ace) (suit Spades))} and pattern-match on its fields.
|
||||||
- Trait implementations are automatic: Although @code{->str} was not explicitly listed in the export list, its implementations still work in @code{game.bjo}. Because @code{impl} blocks define anonymous implementations rather than named bindings, they cannot be exported individually; they are automatically carried along whenever the module defining them is loaded.
|
- Trait implementations are automatic: Although @code{Displayed} was not explicitly listed in the export list, its implementations still work in @code{game.bjo}. Because @code{impl} blocks define anonymous implementations rather than named bindings, they cannot be exported individually; they are automatically carried along whenever the module defining them is loaded.
|
||||||
- Private definitions: Unexported bindings like @code{all-suits} remain private to @code{deck.bjo} and are inaccessible from outside.
|
- Private definitions: Unexported bindings like @code{all-suits} remain private to @code{deck.bjo} and are inaccessible from outside.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -148,7 +152,7 @@ so its representation is visible only to the code of pile. A value of it can be
|
||||||
passed on here, and built and taken apart through the functions that module exports.
|
passed on here, and built and taken apart through the functions that module exports.
|
||||||
}|
|
}|
|
||||||
|
|
||||||
Even though the type's representation is private, any trait implementations defined in @code{pile.bjo} remain available to consumers. For instance, an @code{(impl (->str Pile) ...)} inside @code{pile.bjo} allows any importing module to convert a @code{Pile} to a string.
|
Even though the type's representation is private, any trait implementations defined in @code{pile.bjo} remain available to consumers. For instance, an @code{(impl (Displayed Pile) ...)} inside @code{pile.bjo} allows any importing module to print a @code{Pile}. Without one, a @code{Pile} is written as @code{#<Pile>}, so printing it shows nothing of its representation.
|
||||||
|
|
||||||
@subsubsection{Import modifiers}
|
@subsubsection{Import modifiers}
|
||||||
|
|
||||||
|
|
@ -221,7 +225,7 @@ While @code{export} publishes definitions authored in the current module, @code{
|
||||||
(re-export Card Rank Suit full-deck shuffle Pile new-pile draw)
|
(re-export Card Rank Suit full-deck shuffle Pile new-pile draw)
|
||||||
}|
|
}|
|
||||||
|
|
||||||
Re-exporting a type exposes the exact original type, not a distinct copy; a @code{Card} referenced via @code{cards.bjo} is identical to one from @code{deck.bjo}. However, trait implementations (@code{impl}) do not transfer through re-exports. They remain bound to the module where they were defined, meaning any file that wants to format cards with @code{->str} must still import @code{deck.bjo}.
|
Re-exporting a type exposes the exact original type, not a distinct copy; a @code{Card} referenced via @code{cards.bjo} is identical to one from @code{deck.bjo}. However, trait implementations (@code{impl}) do not transfer through re-exports. They remain bound to the module where they were defined, meaning any file that wants to print cards with their @code{Displayed} implementation must still import @code{deck.bjo}.
|
||||||
|
|
||||||
You can provide alternative names for bindings or macros using @code{(:alias new old)}. Combining @code{:alias} with @code{export} or @code{re-export} allows libraries to offer cleaner or more idiomatic public APIs:
|
You can provide alternative names for bindings or macros using @code{(:alias new old)}. Combining @code{:alias} with @code{export} or @code{re-export} allows libraries to offer cleaner or more idiomatic public APIs:
|
||||||
|
|
||||||
|
|
@ -471,7 +475,7 @@ To illustrate, we can add a persistent high-score ledger to our card game. Each
|
||||||
(defun (main)
|
(defun (main)
|
||||||
(save-score "ada" 31)
|
(save-score "ada" 31)
|
||||||
(save-score "bo" 27)
|
(save-score "bo" 27)
|
||||||
(println (read-scores)) ;; [(ada, 31) (bo, 27)]
|
(println (read-scores)) ;; [(Tuple ada 31) (Tuple bo 27)]
|
||||||
0)
|
0)
|
||||||
}|
|
}|
|
||||||
|
|
||||||
|
|
@ -1730,7 +1734,7 @@ Invalid literal syntax is flagged with a compile-time error at the point of use:
|
||||||
The hash macro 'card' failed at bad.bjo:3: a suit is clubs, diamonds, hearts or spades — in hurts
|
The hash macro 'card' failed at bad.bjo:3: a suit is clubs, diamonds, hearts or spades — in hurts
|
||||||
}|
|
}|
|
||||||
|
|
||||||
Notice the @code{re-export} in @code{cardmacros.bjo}: because the macro expansion emits constructor symbols like @code{Card} and @code{Queen}, those types must be resolvable in the module invoking the macro. Re-exporting them ensures that importing @code{cardmacros.bjo} alone brings the required types into scope. Importing @code{deck.bjo} alongside it remains recommended so that the corresponding @code{->str} implementations are also present.
|
Notice the @code{re-export} in @code{cardmacros.bjo}: because the macro expansion emits constructor symbols like @code{Card} and @code{Queen}, those types must be resolvable in the module invoking the macro. Re-exporting them ensures that importing @code{cardmacros.bjo} alone brings the required types into scope. Importing @code{deck.bjo} alongside it remains recommended so that the corresponding @code{Displayed} implementations are also present.
|
||||||
|
|
||||||
@subsubsection{Literals read by a grammar}
|
@subsubsection{Literals read by a grammar}
|
||||||
|
|
||||||
|
|
@ -1850,7 +1854,7 @@ ok: an ace is worth 11
|
||||||
ok: shuffling keeps the cards
|
ok: shuffling keeps the cards
|
||||||
}|
|
}|
|
||||||
|
|
||||||
Whenever an assertion fails, the output reports both the computed value and the expected value: @code{FAILURE: deliberately wrong gave 2 but wanted 3}.
|
Whenever an assertion fails, the output reports both the computed value and the expected value, each as @code{written} gives it: @code{FAILURE: deliberately wrong gave 2 but wanted 3}, or @code{gave "4" but wanted "5"} for strings. So @code{expect} needs a @code{Written} for the type, which every type has.
|
||||||
|
|
||||||
For asynchronous or resource-intensive testing, @code{(with-test "name" body ...)} executes the test body within an isolated structured concurrency scope configured with a default deadline. Any background fibers spawned during the test are automatically awaited, and the test fails if unclosed file ports remain open when the scope exits. Because @code{with-test} forms a concurrency scope, it must be invoked from within a bjoroutine.
|
For asynchronous or resource-intensive testing, @code{(with-test "name" body ...)} executes the test body within an isolated structured concurrency scope configured with a default deadline. Any background fibers spawned during the test are automatically awaited, and the test fails if unclosed file ports remain open when the scope exits. Because @code{with-test} forms a concurrency scope, it must be invoked from within a bjoroutine.
|
||||||
|
|
||||||
|
|
@ -1885,14 +1889,18 @@ You can launch an interactive Read-Eval-Print Loop by running @code{bjo repl}. W
|
||||||
bjo> (+ 1 2)
|
bjo> (+ 1 2)
|
||||||
3
|
3
|
||||||
bjo> (import "deck.bjo")
|
bjo> (import "deck.bjo")
|
||||||
bjo> (vec-slice (full-deck) 0 3)
|
bjo> (vec-slice (full-deck) 0 2)
|
||||||
[2♣ 3♣ 4♣]
|
[(Card (rank (Pip 2)) (suit Clubs)) (Card (rank (Pip 3)) (suit Clubs))]
|
||||||
|
bjo> (displayed (vec-slice (full-deck) 0 3))
|
||||||
|
"[2♣ 3♣ 4♣]"
|
||||||
bjo> (defun (double (: x int)) : int (* x 2))
|
bjo> (defun (double (: x int)) : int (* x 2))
|
||||||
double
|
double
|
||||||
bjo> (double 21)
|
bjo> (double 21)
|
||||||
42
|
42
|
||||||
}|
|
}|
|
||||||
|
|
||||||
|
The REPL shows a result with @code{written}, as the reader reads it: a string in quotes, a card as it is built. @code{println} and @code{displayed} give the text a person reads.
|
||||||
|
|
||||||
Unlike many Lisp REPLs, Bjolang's REPL does not interpret forms. Instead, each entered interaction is compiled on the fly into an in-memory assembly using the exact same compiler pipeline applied to files on disk, ensuring identical semantics between interactive sessions and compiled production builds.
|
Unlike many Lisp REPLs, Bjolang's REPL does not interpret forms. Instead, each entered interaction is compiled on the fly into an in-memory assembly using the exact same compiler pipeline applied to files on disk, ensuring identical semantics between interactive sessions and compiled production builds.
|
||||||
|
|
||||||
Top-level functions declared in the REPL require explicit type signatures, mirroring the rules for modules. You can supply parameter and return types inline directly on the @code{defun}, or submit a standalone signature on its own line immediately preceding the definition:
|
Top-level functions declared in the REPL require explicit type signatures, mirroring the rules for modules. You can supply parameter and return types inline directly on the @code{defun}, or submit a standalone signature on its own line immediately preceding the definition:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue