commit e54bd31d698962a8896e449cb726c2ace0935eeebd38c2a28fc41d7d99fd69d8 Author: Linus Björnstam Date: Thu Oct 1 11:36:18 2026 +0200 first commit diff --git a/_01.Language-basics.sz b/_01.Language-basics.sz new file mode 100644 index 0000000..76122e4 --- /dev/null +++ b/_01.Language-basics.sz @@ -0,0 +1,2131 @@ +@section{Getting started} + +Bjolang needs only two things: dotnet 10 and an f# compiler. Once those are installed you build the compiler using dotnet build -c Release, run ./build_std.sh and ./run_tests.py and ./run_bjo_tests.py in the main directory. Then you can use the bjo executable in the bjo directory. Personally I alias a shell command to the path of bjo, since there currently is no way to install it properly. + +@subsection{Your first bjolang program} + +Bjolang compiles to c#. Many of the design decisions behind bjolang stems from this limitation. The first one you will encounter is that a program needs a main function. like the one in c#, that returns an int. If execution ended correctly it should return 0 if the program executed successfully. + +@codeblock[#:lang "bjolang"]{ +(: print-hello (-> string unit)) +(defun (print-hello name) + (println (str "Hello, " name "!"))) + +(defun (main args) + (print-hello "User") + 0) +} + +This can then be run using @code{bjo run filename}, and it should print @code{Hello, User!}. + +What you can see here is that defun is the form for defining a function. main is special, it needs no type declaration. print-hello is a regular unction, and thus has to declare it's type. Everything at toplevel needs to do that. The form is (: name type). A function type is declared as (-> ArgType ... Return-Type). print-hello takes a string argument and returns unit. unit is the name for nothing. + +@subsection{Declaring types} +For most of your bjolang code, you will only declare types in the top level. Types within functions are inferred, so declaring types there is almost never necessary. A value type is declared just like a function, but without the function type declaration: + +@codeblock[#:lang "bjolang"]{ +(: usermap (Map Keyword String)) +(def usermap #map((:admin "Linus") (:moderator "Sunil") (:janitor "Björnstam"))) +} + +You can also declare your own types. Either aliases of other types, records of your own, or union types. + +@codeblock[#:lang "bjolang"]{ +(type + (: Suit (Union Hearts Diamons Clubs Spades)) + (: Card (Struct (: rank byte) (: suit Suit)) + (: User (Record (: name string) + (: age byte) + (: favourite-playing-card Card))))) +} + +This can then be destructed using pattern matching. + +@codeblock[#:lang "bjolang"]{ +(: print-user (-> User unit)) +(defun (print-user u) + (match u + ;; Subpatterns: here we match name, age, the Card rank + ;; but only succeed if the Suit is Hearts. + ((User name age (Card rank (Suit Hearts))) + (println "Oh my god, we got hearts!")) + ((User (age 100)) + (println "I have no idea which card, but the user is old")) + ((User name) (println "The user is called ${name}.")) + ;; pattern matching does it's best to do exhaustiveness checks. + ;; and while it is usually pretty good at figuring out whether + ;; patterns are exhaustive, sometimes it cannot. Then you will have to add + ;; a match-all + (_ (panic! "This should not happen.")))) +} + + + +@subsubsection{Editor support} + +Bjolang was written in emacs, and emacs is currently the only editor with limited bjolang support through the file bjomode.el in the main github repository. It lacks any fancy thins, but handles a modicum of indentation and syntax highlighting. + +@subsection{Values, types and signatures} +These are the bjolang literals: +@read-table{ +| Type | Form | +| int,byte,long, ulong uint | 1 0 -4 200000 | +| long | -5000000000L | +| ulong | 50000000000UL | +| double | 1.234 | +| string | "hej" | +| symbol | 'hej | +| keyword | #:keyword :keyword (these are equal) | +| character | #\space #\a #\newline | +| bool | #t #f | +| unit/void | unit | + +} + +Then there are some special cases, like string interpolation: + +@codeblock[#:lang "bjolang"]{ +(let ((an-int 5) + (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)))) +} + +@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{->}: + +@codeblock[#:lang "bjolang"]{ + +(: double (-> int int)) +(defun (double x) + ;; the last expression in the body is the return value + (* x 2)) + +(:print-n-times (-> string int unit)) +(defun (print-n-times what n) + (loop (:for i (up-from 0 #:to n)) + (println what))) + +;; call twice takes a function take takes an argument +;; of type %a and returns unit. +(: call-twice (-> (-> %a unit) %a unit)) +(defun (call-twice f val) + (f val) + (f val)) +} + +Bjolang also supports keyword arguments. The caller do not have to supply them, and if they are not supplied they are bound to a default expression that is evaluated in the function body. + +@codeblock[#:lang "bjolang"]{ +(: n-times (-> int (#:what string) string)) +(defun (n-times n #:what (string-append "place" "holder")) + (def sb (stringbuilder-empty)) + (loop (:for i (up-from 0 #:to n)) + (stringbuilder-add-string! sb placeholder)) + (stringbuilder->string sb)) + +;; Calling it now returns: +(n-times 2) ;; -> "placeholderplaceholder" +(n-times 500 #:what "hej!\n") ;; -> "hej!\nhej!\nhej!\n..." +} + +The default expression of the keyword argument is evaluated in the body of the function every time the function is called without a supplied argument. Keywords must also be in the same order in the signature and definition. A function with keywords yields two c# bodies, one with optional arguments and one with the default expressions, UNLESS the default arguments are c# literals. Then the function is a regular c# function with named arguments. + + +@subsubsection{Inference} + +Everything at the toplevel needs a signature, even values. In a function body, nothing strictly needs a signature. Function bodies are inferred using Hindley Milner type inference. This is of course comfortable, but also has some limits. They type of an undeclared type is inferred at first usage. + +@codeblock[#:lang "bjolang"]{ +;; pretend we are in a function body +(def a (transientmap-empty)) +(add! a (:key . 5)) ;; transientmap is pinned to (Transientmap Keyword int). +(add! a (:key2 . "hej")) ;; fails +} + +The example above is of course facile, but it matters more for generic functions with inlined traits. They are pinned to the first use, and any subsequent usage will raise an error. More on this in the trait section. + +@subsubsection{Naming conventions} + +Bjolang follows scheme's naming conventiens. Predicates end with @code{?}. Mutating functions end with @code{!}. Conversions are written using @code{->}. + +While the base types are lowercase (which is a design mishap), types should be capitalized. Record and struct fields are lower case. + +@subsection{Functions} + +As you have seen already, functions are declared using @code{defun}. Bjolang also has anonymous functions defined using @code{fun}: + +@codeblock[#:lang "bjolang"]{ +(list-map (fun (x) (* x x)) (list 1 2 3 4)) ;; -> '(1 4 9 16). +;; there is also a lambda shorthand where argument are numbered +;; using &. & = &1. + +(println (#(+ & &2) 2 3)) ;; -> prints 5. +(println (#(+ &1 &3) 1 2 3)) ;; -> prints 4. +} + +Functions can also be generic, to handle any kind of argument. This is useful if you have a function that does not need to inspect the values. + +@codeblock[#:lang "bjolang"]{ +(: make-tuple (-> (List %a) %b (Tuple %a %b))) +(defun (make-tuple ls val) + (match ls + ((Cons car _) (Tuple car val)) + (_ (panic! "this did not work as expected.")))) +} + +Generics can also be constrained to work on Traits. For example this signature, for a function that can compare the first element of a tuple with the elements of a Vec. + +@codeblock[#:lang "bjolang"]{ +(: (-> (Tuple %a %b) (Vec %a) bool) (where (Eq %a))) +} + +@subsubsection{Local functions} + +A local function looks exactly like a regular functions, but does not need a type signature. In fact, it does not even support (: ...) signatures. When needed you can use inline signatures, which TODO: WRITE INLINE SIGNATURES EXPLANATION. + +@subsection{Bindings and control flow} + +@subsubsection{binding forms} + +Variables are bound using, primarily, four forms @code{def}, @code{def*} @code{let}, @code{let*}. @code{let} and @code{let*} are the same as in scheme. @code{let} binds values in parallel. @code{let*} binds sequentially. + +@codeblock[#:lang "bjolang"]{ + +(def a 1) +(def b 2) + +(let ((a 4) (b (+ a 1))) + (println a) ;; -> 4 + (println b)) :: -> 5 + +(let* ((a 4) (b (+ a 1))) + (println a) ;; 4 + (println b)) ;; 5 +} + +@code{def*} is a form that mostly just saves on key presses: + +@codeblock[#:lang "bjolang"]{ +(def* (a 5) + (b 6) + (c (+ a b))) +} + +@subsubsection{conditional executions - if, cond, when, unless, and case} + +The base form for executing code conditionally is @code{(if test-expr true-expr false-expr)}. The prelude defines the cond macro that allows for chaining tests: + +@codeblock[#:lang "bjolang"]{ + +(def weather (get-weather-symbol)) +(if (= weather 'rain) + (get-umbrella) + (if (= weather 'windy) + (get-jacket) + (get-shorts))) +;; can be written as +(cond + ((= weather 'rain) (get-umbrella)) + ((= weather 'windy) (get-jacket)) + (else (get-shorts))) + +;; which can be written as + +(case weather + ('rain (get-umbrella)) + ('windy (get-jacket)) + (else (get-shorts))) +} + +For the cases where code starts drifting rightward, there are two special clauses in the cond macro, @code{:def} and @code{:do}. This is taken from actual bjolang code. + +@codeblock[#:lang "bjolang"]{ +(cond + ((> depth json-max-depth) (Err (too-deep rd "array"))) + ;; this executes two things and binds the variable `mark` before continuing + (:do + (step! rd) + (skip-space! rd)) + (:def mark (record-ref rd top)) + ((at-end? rd) (Err (at rd "the document ended inside an array"))) + ((char=? (peek rd) #\]) (step! rd) (Ok (JsonArr (harvest! rd mark)))) + (else ...)) +} + +The above is usually a code smell, but can be useful in managing the kind code drift that sometimes plagues lisp code. + +@subsubsection{with-return and bailing from failed bindings} + +Bjolang has 2 solutions to the problem that is sometimes solved by do-notation in functional languages. Thi @code{(with-return ret body ...} form binds an espace function to ret, which means code within it can return from the body (but not within local functions). This is often used with the destructuring of the def and def* forms. + +Within a with-return you can use the bound label to directly return a value from the with-return block + +@codeblock[#:lang "bjolang"]{ +(with-return ret + (def a (read-line)) + (when (= "" a) + (ret 0)) + (println "hej hopp hej hej") + (string-count a)) +} + +The def forms have a particular thing that they do when they are usid with refutable patterns (patterns that can fail). Then they take one of four extra clauses: + +@codeblock[#:lang "bjolang"]{ + +(: do-things (-> (List int) (Result string string))) +(defun (do-things ls) + ;; Try to match a list with 3 elements and return an Err if it fails + (def (List a b c) ls :leave-with (Err "The string did not have 3 elements")) + ;; Try get the key "a" from a map and leave with err + (def (Some elem) (try-ref my-map a) :leave-with (Err "The map lacked a key")) + ;; open the filename in `elem` and propagate any error. + (def (Ok port) (open-input-file elem) :propagate) + (println "Here we know port is bound. ") + (Ok "the file exists")) + +} + +The other clauses are @code{:default expr} that binds the variable to expr if the match fails. This only works with patterns that bind one variable. Then there is @code{:leave} which take the other pattern arms: + +@codeblock[#:lang "bjolang"]{ +(def (Ok b) expr :leave ((Err "ojdå") (println "special swedish error encountered") expr) + ((Err b) expr)) +} + +@subsubsection{Mutable bindings} +Mutable bindings are defined with @code{def/mutable}. They can be mutated with the @code{set!} form. Generic local mutable variables generalize at the first @code{set!}, and top-level generic bindings are not allowed. + +@codeblock[#:lang "bjolang"]{ +(defun (main args) + (def/mutable b 0) + (loop (:for a args) + (:when (my-special-predicate? a)) + (set! b (+ b a))) + b) +} + + +@subsection{Your own types} +A program without your own types are boring. + +@subsubsection{Aliases} +A type alias is a different name for a type that already exists. It is declared with the @code{type} form. + +@codeblock[#:lang "bjolang"]{ +(type (: Stringalias string)) +} + +@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. + +@codeblock[#:lang "bjolang"]{ +(type + (: Ripeness (Union Ripe Megaripe Rotten)) + (: Fruit (Record (: name string) + (: amount int) + (: ripeness Ripeness)))) + +(def banana (Fruit (name "banana") (amount 5) (ripeness Rotten))) +} + +A field can be marked #:mutable, to make the record (not struct) mutable. This however makes using the record in places that requires hashing fail at runtime. The mutable fields can be updated with @code{record-set!}. + +@codeblock[#:lang "bjolang"]{ +(type (: Reader (: pos integer #:mutable) + (: port TextInputPort))) + +... +(record-set! my-reader pos (+ 1 (record-get my-reader pos))) +} + +@subsubsection{Unions} + +A union is a type whose values are one of several cases. A case either carries nothing, and is then written as a bare name, or it carries a payload, and is then written like a record field: @code{(: CaseName type ...)}. + +@codeblock[#:lang "bjolang"]|{ +(type + ;; Four cases that carry nothing. + (: Suit (Union Clubs Diamonds Hearts Spades)) + ;; A card's rank is either a number card, carrying its number, or a court card. + (: Rank (Union (: Pip int) Jack Queen King Ace)) + ;; A case may carry more than one value. + (: Shape (Union (: Circle double) (: Rect double double) Empty))) +}| + +A case is also its constructor. A case without a payload is a value, and a case with a payload is a function that builds one: + +@codeblock[#:lang "bjolang"]|{ +(def trump Hearts) ;; Suit +(def seven (Pip 7)) ;; Rank +(def square (Rect 2.0 2.0)) ;; Shape + +;; Since (Pip ...) is a function, it can be passed around like one: +(list-map Pip (list 2 3 4)) ;; a (List Rank) of three number cards +}| + +You take a union apart with @code{match}, which gets its own section below. The short version is that every case gets a clause, and the payload is bound to names: + +@codeblock[#:lang "bjolang"]|{ +(: area (-> Shape double)) +(defun (area s) + (match s + ((Circle r) (* 3.14159 (* r r))) + ((Rect w h) (* w h)) + (Empty 0.0))) + +(area (Rect 2.0 3.0)) ;; 6.0 +}| + +The compiler checks that a match covers every case. If you add a @code{Triangle} to @code{Shape}, every match on a shape that does not handle it becomes a compile error, which is most of the reason to use a union in the first place. + +Unions can be generic. The type variables go after the type name, just like for records: + +@codeblock[#:lang "bjolang"]|{ +(type (: (Maybe %a) (Union Nothing (: Just %a)))) +(type (: (Either %l %r) (Union (: Left %l) (: Right %r)))) + +(: maybe-first (-> (List %a) (Maybe %a))) +(defun (maybe-first xs) + (match xs + (() Nothing) + ((Cons x _) (Just x)))) +}| + +You never have to write these two, since the prelude already has them under the names @code{(Option %a)}, with the cases @code{None} and @code{Some}, and @code{(Result %e %a)}, with the cases @code{Err} and @code{Ok}. They are ordinary unions, and they get a section of their own further down. + +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. +} + +@subsubsection{Types that refer to themselves and each other} + +A type may mention itself. This is how you write trees, expression languages and anything else recursive: + +@codeblock[#:lang "bjolang"]|{ +(type (: (Tree %a) (Union Leaf (: Node (Tree %a) %a (Tree %a))))) + +(: tree-insert (-> int (Tree int) (Tree int))) +(defun (tree-insert x t) + (match t + (Leaf (Node Leaf x Leaf)) + ((Node l v r) + (cond ((< x v) (Node (tree-insert x l) v r)) + ((> x v) (Node l v (tree-insert x r))) + (else t))))) + +(: tree->list (-> (Tree %a) (List %a))) +(defun (tree->list t) + (match t + (Leaf '()) + ((Node l v r) (list-append (tree->list l) (Cons v (tree->list r)))))) + +(tree->list (list-foldl tree-insert Leaf (list 5 2 8 1 9 2))) ;; '(1 2 5 8 9) +}| + +Types may also refer to each other. All the types of a module are known before any of them is checked, so the order they are declared in does not matter, and they do not even have to be in the same @code{type} form: + +@codeblock[#:lang "bjolang"]|{ +(type (: Expr (Union (: Num int) + (: Add Expr Expr) + (: Let Binding Expr) + (: Var string)))) + +(type (: Binding (Record (: name string) (: value Expr)))) +}| + +@subsubsection{Deriving equality and ordering} + +Every type can be compared with @code{=}. By default a record, struct or union is compared field by field. To use your type as a key in a hash map, or to sort it, it is better to say so explicitly with @code{type/derive}. It is written like @code{type}, with a list of traits in front: + +@codeblock[#:lang "bjolang"]|{ +(type/derive (Eq Ord) + (: Suit (Union Clubs Diamonds Hearts Spades)) + (: Rank (Union (: Pip int) Jack Queen King Ace)) + (: Card (Struct (: rank Rank) (: suit Suit)))) +}| + +@code{Eq} gives you @code{=} and a matching hash. @code{Ord} gives you an ordering, and with it @code{compare}, @code{less?}, @code{list-sort}, @code{list-max} and everything else that orders things. The derived order is the obvious one: + +@read-list{ +- A union orders by case first, in the order the cases are declared, so above @code{Clubs} is less than @code{Spades} and every @code{Pip} is less than @code{Jack}. Two values of the same case are ordered by their payload. +- A record or struct compares its fields in declaration order and stops at the first one that differs. Above, cards are ordered by rank first and by suit when the ranks are equal. +} + +@codeblock[#:lang "bjolang"]|{ +(list-sort (list (Card (rank Ace) (suit Spades)) + (Card (rank (Pip 7)) (suit Hearts)) + (Card (rank (Pip 10)) (suit Clubs)) + (Card (rank (Pip 7)) (suit Clubs)))) +;; 7♣ 7♥ 10♣ A♠ + +(less? (Card (rank (Pip 2)) (suit Spades)) + (Card (rank Jack) (suit Clubs))) ;; #t: rank decides first +}| + +Note that @code{<} and @code{>} are not in that list. They are for numbers and characters only. For anything with an @code{Ord}, use @code{less?}, @code{greater?}, @code{at-most?} and @code{at-least?}, or @code{compare}, which returns a negative number, zero or a positive number. + +When the derived order is not the one you want (for example if aces should sometimes be low), you write the implementation by hand. That is covered in the traits section. + +@subsubsection{The card game} + +The rest of this chapter uses a card game as a running example, so here are its types in one place. Put this at the top of a file and the examples in the following sections can be pasted below it. + +@codeblock[#:lang "bjolang"]|{ +(type/derive (Eq Ord) + (: Suit (Union Clubs Diamonds Hearts Spades)) + (: Rank (Union (: Pip int) Jack Queen King Ace)) + (: Card (Struct (: rank Rank) (: suit Suit)))) + +(type (: Player (Record (: name string) + (: hand (Vec Card)) + (: 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 "♠")))) + +(impl (->str Rank) + (defun (->str r) + (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)}")) + +(println (Card (rank Queen) (suit Hearts))) ;; Q♥ +}| + +@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}: + +@codeblock[#:lang "bjolang"]|{ +(: won-trick (-> Player Player)) +(defun (won-trick p) + (record-set p (tricks (+ 1 (record-ref p tricks))))) + +(def ada (Player (name "ada") (hand []) (tricks 0))) +(record-ref (won-trick (won-trick ada)) tricks) ;; 2 +(record-ref ada tricks) ;; still 0 +}| + +@subsection{Pattern matching} + +Pattern matching is how you take values apart in bjolang. There is no @code{car}/@code{cdr} style of programming on unions: you say what shape you expect and the compiler binds the parts for you, and checks that you did not forget a shape. + +@subsubsection{match} + +@code{(match value clause ...)} tries each clause in turn. A clause is a pattern followed by a body, and the first clause whose pattern fits is the one that runs. Its last expression is the value of the whole match. + +The basic patterns are: + +@read-table{ +| Pattern | Matches | +|--------------------------------------------+------------------------------------------------------| +| @code{42} @code{"hi"} @code{#\a} @code{#t} | exactly that literal | +| @code{:red} @code{'sym} | exactly that keyword or symbol | +| @code{_} | anything, and binds nothing | +| @code{name} | anything, and binds it to @code{name} | +| @code{Hearts} | the case @code{Hearts} (capitalised: a constructor) | +| @code{(Pip n)} | the case @code{Pip}, binding its payload to @code{n} | +} + +The difference between a binder and a constructor is the first letter. A lowercase name binds, a capitalised name is a case. This is also the source of one classic mistake: if you misspell a case, or forget to import it, it becomes a binder that matches everything. The compiler will usually tell you, because the clauses below it can then never run. + +@codeblock[#:lang "bjolang"]|{ +(: light (-> Keyword string)) +(defun (light k) + (match k + (:red "stop") + (#:green "go") ;; :green and #:green are the same keyword + (_ "wait"))) + +(: rank-points (-> Rank int)) +(defun (rank-points r) + (match r + ((Pip n) n) + (Ace 11) + (_ 10))) +}| + +Patterns nest. A payload can itself be matched against a pattern: + +@codeblock[#:lang "bjolang"]|{ +(match (Some (Pip 10)) + ((Some (Pip 10)) "a ten") + ((Some (Pip n)) #"the number ${n}") + ((Some court) "a court card or an ace") + (None "no card")) +}| + +@subsubsection{Records and structs} + +A record or struct is matched by its type name followed by the fields you care about, each written @code{(field pattern)}. Fields you leave out match anything, and the order does not matter. A field name on its own binds the field to a variable of the same name, which is by far the most common way to use it: + +@codeblock[#:lang "bjolang"]|{ +(: card-name (-> Card string)) +(defun (card-name c) + (match c + ((Card (rank Ace) (suit Spades)) "the ace of spades") + ((Card (rank (Pip 10))) "a ten") + ;; `suit` alone binds the suit field to the name suit + ((Card (rank (Pip n)) suit) #:when (< n 5) #"a low card (${n}${suit})") + ((Card (rank (or Jack Queen King))) "a court card") + ((Card rank) #"something else: ${rank}"))) + +(: player-line (-> Player string)) +(defun (player-line p) + (match p + ((Player name (tricks 0)) #"${name} has not won anything") + ((Player name tricks) #"${name} has won ${tricks} tricks"))) +}| + +A bare type name, like @code{Card}, is a pattern that matches every card. + +@subsubsection{Lists, vecs, arrays and tuples} + +Collections have patterns too. @code{...} after the last name binds the rest of the collection: + +@read-table{ +| Pattern | Matches | +|--------------------------------------------+------------------------------------------| +| @code{()} or @code{(List)} | the empty list | +| @code{(List a b)} | a list of exactly two elements | +| @code{(List first rest ...)} | a list of at least one element | +| @code{(Cons head tail)} | a non-empty list, split in head and tail | +| @code{[]} @code{[a b]} @code{[a rest ...]} | the same for a @code{Vec} | +| @code{#[]} @code{#[a rest ...]} | the same for an @code{Array} | +| @code{(a . b)} or @code{(Tuple a b)} | a pair | +} + +@codeblock[#:lang "bjolang"]|{ +(: describe (-> (List int) string)) +(defun (describe xs) + (match xs + (() "empty") + ((List x) #"one: ${x}") + ((List 0 rest ...) "starts with zero") + ((List x y rest ...) #"${x}, ${y} and ${(list-length rest)} more"))) + +(describe (list 1 2 3 4)) ;; "1, 2 and 2 more" + +(: header (-> (Vec string) string)) +(defun (header v) + (match v + ([] "no columns") + ([only] only) + ([first rest ...] #"${first} (+${(vec-length rest)})"))) + +(: pair (-> (Tuple int string) string)) +(defun (pair p) + (match p + ((0 . s) #"zero and ${s}") + ((_ . "x") "an x") + ((n . s) #"${n}/${s}"))) +}| + +The rest of a vec pattern shares its structure with the original vec, so it is cheap. The rest of an array pattern is a copy. + +@subsubsection{Guards, or and and} + +A clause may have a guard, written @code{#:when expr} after the pattern. The clause only matches if the pattern fits and the guard is true, and the guard can use everything the pattern bound: + +@codeblock[#:lang "bjolang"]|{ +(match hand-size + (0 "no cards") + (n #:when (> n 10) "too many cards") + (n #:when (even? n) "an even hand") + (_ "an odd hand")) +}| + +@code{(or p ...)} matches if any of the alternatives match. The alternatives may not bind names, since there would be no way to know which of them did. @code{(and p ...)} matches if all of them match, which is mostly useful to bind a name to the whole value while also taking it apart: + +@codeblock[#:lang "bjolang"]|{ +(: red? (-> Suit bool)) +(defun (red? s) + (match s + ((or Hearts Diamonds) #t) + (_ #f))) + +(match (Some 3) + ((and (Some n) whole) #"${n} inside ${whole}") ;; "3 inside (Some 3)" + (None "none")) +}| + +@subsubsection{Views, type tests and Has} + +@code{(:view f pattern)} calls @code{f} on the value and matches the result against @code{pattern}. That lets a match look at something that is not directly in the value, like whether a string parses as a number: + +@codeblock[#:lang "bjolang"]|{ +(: parse-int (-> string (Option int))) +(defun (parse-int s) + (match (try (string->int s) #:catch (System.FormatException)) + ((Ok n) (Some n)) + ((Err _) None))) + +(: classify (-> string string)) +(defun (classify s) + (match s + ((:view parse-int (Some n)) #:when (even? n) "an even number") + ((:view parse-int (Some n)) "an odd number") + ((:view string-length 0) "nothing") + (_ "text"))) +}| + +@code{(:is Type name)} tests the .NET type of a value, and binds it with that type. It is mostly used to tell exceptions apart, and there is an example of that in the section on failure. + +@code{(Has key pattern)} matches any collection you can look things up in (maps, vecs) that has @code{key}, and matches the value under it against @code{pattern}. @code{Has*} takes several key and pattern pairs: + +@codeblock[#:lang "bjolang"]|{ +(: port-of (-> (Map string int) int)) +(defun (port-of settings) + (match settings + ((Has "port" p) p) + (_ 8080))) + +(: user-line (-> (Map string string) string)) +(defun (user-line m) + (match m + ((Has* ("name" n) ("city" c)) #"${n} from ${c}") + ((Has "name" n) n) + (_ "anonymous"))) +}| + +@subsubsection{Exhaustiveness} + +Every match is checked, and a match that does not cover every value is a compile error. The error names a value that falls through: + +@codeblock[#:lang "bjolang"]|{ +(: red? (-> Suit bool)) +(defun (red? s) + (match s + (Hearts #t) + (Diamonds #t) + (Clubs #f))) +}| + +@codeblock[#:lang "text"]|{ +Pattern Error at cards.bjo:8: this match does not cover every value. Spades reaches no clause. +}| + +The counterexample is a real value, and for nested patterns it tells you exactly which combination is missing: + +@codeblock[#:lang "bjolang"]|{ +(match c + ((Card (rank (Pip n))) n) + ((Card (rank (or Jack Queen King))) 10) + ((Card (rank Ace) (suit Spades)) 50)) +}| + +@codeblock[#:lang "text"]|{ +Pattern Error at cards.bjo:8: this match does not cover every value. (Card (rank Ace) (suit Clubs)) reaches no clause. +}| + +A clause that can never run, because the clauses above it already match everything it would, is also an error: + +@codeblock[#:lang "text"]|{ +Pattern Error at cards.bjo:5: this clause can never run — the clauses above it already match everything it does. +}| + +The checker does not run your code, so a clause with a @code{#:when} guard, a @code{:view} or an @code{:is} covers nothing as far as it is concerned. A match that only has guarded clauses needs a final catch-all: + +@codeblock[#:lang "bjolang"]|{ +(match n + (x #:when (< x 10) "small") + (_ "large")) ;; without this line:"_ reaches no clause" +}| + +@subsubsection{case} + +@code{case} is a short form for the common match against literals. Each clause is a list of values, and @code{else} is the catch-all: + +@codeblock[#:lang "bjolang"]|{ +(: nth-name (-> int string)) +(defun (nth-name n) + (case n + ((1) "first") + ((2) "second") + ((3) "third") + (else #"${n}th"))) + +(case command + (("quit" "exit") (stop)) + (("help") (show-help)) + (else (run command))) +}| + +The data in a clause must be literals: numbers, strings, characters, booleans, keywords or quoted symbols. A union case like @code{Hearts} is not allowed, since @code{case} could not tell it from a binder. For those, use @code{match} with @code{or}. + +@subsubsection{Patterns in def} + +Any pattern can also be used in @code{def}. You have already seen that in the section on @code{with-return}, where a pattern that may fail gets a failure part. A pattern that cannot fail needs none, and is simply a way to take something apart without indenting the rest of the function: + +@codeblock[#:lang "bjolang"]|{ +(def (Card rank suit) c) ;; binds rank and suit +(def (who . points) (Tuple "ada" 3)) ;; binds who and points +}| + +A pattern that may fail needs one of the failure parts: @code{:leave-with}, @code{:leave}, @code{:propagate} or @code{:default}. They are all covered in the section on bailing out of failed bindings. Here is one of each of the two most common: + +@codeblock[#:lang "bjolang"]|{ +(: score-line (-> (Map string int) string string)) +(defun (score-line scores who) + ;; If the lookup fails, the function's value is the :leave-with value. + (def (Some n) (map-try-ref scores who) :leave-with #"${who} has not played") + #"${who}: ${n}") + +(: total (-> (Map string int) string string (Option int))) +(defun (total scores a b) + ;; If either lookup fails, the None is passed on as the function's value. + (def* ((Some x) (map-try-ref scores a) :propagate) + ((Some y) (map-try-ref scores b) :propagate)) + (Some (+ x y))) +}| + +@subsubsection{Example: who wins the trick} + +In many card games, everyone plays one card, and the highest card in the suit that was led (the suit of the first card) wins. Here a trick is a list of who played what, in order: + +@codeblock[#:lang "bjolang"]|{ +(: trick-winner (-> (List (Tuple string Card)) (Option string))) +(defun (trick-winner trick) + (match trick + (() None) + ((Cons (_ . lead) _) + (def led-suit (record-ref lead suit)) + (def best + (loop (:for (who . card) trick) + (:when (= (record-ref card suit) led-suit)) + (:acc top (folding (Tuple "" lead) + (match top + ((_ . top-card) + (if (at-least? card top-card) + (Tuple who card) + top))))) + => top)) + (def (winner . _) best) + (Some winner)))) + +(trick-winner (list (Tuple "ada" (Card (rank (Pip 9)) (suit Hearts))) + (Tuple "bo" (Card (rank Ace) (suit Spades))) + (Tuple "cy" (Card (rank Queen) (suit Hearts))) + (Tuple "dee" (Card (rank (Pip 10)) (suit Hearts))))) +;; (Some "cy"): bo's ace is higher, but it is not a heart. +}| + +The @code{loop} is explained in its own section. What matters here is the patterns: @code{(Cons (_ . lead) _)} picks out the first card of a non-empty list in one go, @code{(:for (who . card) trick)} takes each pair apart as it walks the list, and @code{at-least?} works on cards because @code{Card} derives @code{Ord}. + +@subsection{Options, results and failure} + +Bjolang has no null. A value that may be missing is an @code{Option}, and a computation that may fail returns a @code{Result}. Both are ordinary unions from the prelude: + +@codeblock[#:lang "bjolang"]|{ +;; (Option %a) is either None or (Some value) +;; (Result %e %a) is either (Err error) or (Ok value) +}| + +Exceptions exist too, since everything in .NET throws them, but they are something you turn into a @code{Result} at the edge of your program rather than something you use for ordinary control flow. + +@subsubsection{Option} + +@codeblock[#:lang "bjolang"]|{ +(: halve (-> int (Option int))) +(defun (halve n) + (if (even? n) (Some (/ n 2)) None)) + +(match (halve 7) + ((Some h) #"half is ${h}") + (None "7 is odd")) +}| + +Matching is always possible, but the prelude has shorter ways to say the common things: + +@read-table{ +| Form | Does | +|-------------------------------------+-----------------------------------------------------------------| +| @code{(some? o)} @code{(none? o)} | test which case it is | +| @code{(option-value o fallback)} | the value, or @code{fallback} for @code{None} | +| @code{(option-ref-or o fallback)} | the same | +| @code{(option-ref o)} | the value, and throws for @code{None} | +| @code{(if-let (name o) then else)} | @code{then} with the value bound to @code{name}, or @code{else} | +| @code{(when-let (name o) body ...)} | the body with the value bound, and nothing for @code{None} | +} + +@codeblock[#:lang "bjolang"]|{ +(option-value (halve 7) 0) ;; 0 +(if-let (h (halve 12)) + (println #"half is ${h}") ;; prints "half is 6" + (println "odd")) +(when-let (h (halve 3)) + (println "never printed")) +}| + +@subsubsection{Result} + +A @code{Result} is an @code{Option} that says why it is empty. The error can be any type: a string for a quick script, your own union when the caller needs to act on what went wrong, or a .NET exception. + +@codeblock[#:lang "bjolang"]|{ +(: parse-int (-> string (Result string int))) +(defun (parse-int s) + (match (try (string->int s) #:catch (System.FormatException)) + ((Ok n) (Ok n)) + ((Err e) (Err #"'${s}' is not a number")))) + +(: positive (-> int (Result string int))) +(defun (positive n) + (if (> n 0) (Ok n) (Err #"${n} is not positive"))) + +(ok? (parse-int "12")) ;; #t +(err? (parse-int "twelve")) ;; #t +(result-map #(* & 2) (parse-int "21")) ;; (Ok 42) +}| + +A @code{Result} must be used. In fact every value must be: a form in the middle of a body whose value is dropped is a compile error, unless it is @code{void}. For a @code{Result} that rule is what keeps a failure from disappearing silently. If you really do not care about a value, say so with @code{(ignore expr)}: + +@codeblock[#:lang "text"]|{ +Type Error at game.bjo:4: this value has type int and is discarded. + It is a form in the middle of a body, so its value is dropped. + Every value that is computed and then dropped has to say so: write `(ignore ...)` around it. +}| + +@subsubsection{Chaining things that may fail} + +Most real code does several things in a row, each of which may fail, and stops at the first failure. Nesting matches for that drifts to the right very quickly. There are three ways to write it flat. + +The first is @code{def} with @code{:propagate}. When the pattern does not match, the case that did not match is rebuilt and becomes the return value of the function. With an @code{Option} that is @code{None}, and with a @code{Result} it is the same @code{Err}: + +@codeblock[#:lang "bjolang"]|{ +(: parse-positive (-> string (Result string int))) +(defun (parse-positive s) + (def (Ok n) (parse-int s) :propagate) + (def (Ok p) (positive n) :propagate) + (Ok (* p 10))) + +(parse-positive "12") ;; (Ok 120) +(parse-positive "-4") ;; (Err "-4 is not positive") +(parse-positive "x") ;; (Err "'x' is not a number") +}| + +The second is @code{try->}, a threading form that stops at the first @code{Err}. Each step gets what the previous one returned, unwrapped, in the @code{&} position. @code{some->} is the same for @code{Option}: + +@codeblock[#:lang "bjolang"]|{ +(: parse-positive (-> string (Result string int))) +(defun (parse-positive s) + (try-> s (parse-int &) (positive &))) + +(: quarter (-> int (Option int))) +(defun (quarter n) + (some-> n (halve &) (halve &))) + +(quarter 12) ;; (Some 3) +(quarter 6) ;; None: 3 is odd +}| + +The third is @code{with-return} together with @code{:leave-with}, which was described earlier. Use it when the failures should turn into something that is not the same type, for example a default value or a log line. + +@subsubsection{Exceptions: try, :is and raise} + +.NET code reports failure by throwing. @code{try} turns exceptions of the types you list into an @code{Err}, and the value of a body that did not throw into an @code{Ok}. Anything you did not list keeps propagating as an exception: + +@codeblock[#:lang "bjolang"]|{ +(def divisor (string->int "0")) +(try (/ 10 divisor) #:catch (System.DivideByZeroException)) +;; (Err ), of type (Result System.Exception int) +}| + +Put the @code{try} around the whole region that can fail rather than around each call, and you get a single @code{Result} out. + +@code{#:finally expr} runs @code{expr} however the body ends. Without a @code{#:catch} nothing is caught, and the value is just the body's value: + +@codeblock[#:lang "bjolang"]|{ +(try (render page) + #:finally (println "done rendering")) +}| + +The @code{Err} of a @code{try} holds a @code{System.Exception}. To tell different exceptions apart, use the @code{:is} pattern, which tests the .NET type and binds the value with the more specific type so that you can read its properties. @code{raise} throws an exception again, for the cases that are not yours to handle: + +@codeblock[#:lang "bjolang"]|{ +(: slurp (-> string string)) +(defun (slurp path) + (match (try (file-read-text path) + #:catch (System.IO.FileNotFoundException + System.IO.DirectoryNotFoundException)) + ((Ok text) text) + ((Err (:is System.IO.FileNotFoundException e)) #"no such file: ${(.-FileName e)}") + ((Err (:is System.IO.DirectoryNotFoundException)) "no such directory") + ((Err e) (raise e)))) +}| + +@subsubsection{with-open} + +@code{with-open} binds something disposable, like a port, a stream or a database connection, runs the body, and disposes it however the body ends: + +@codeblock[#:lang "bjolang"]|{ +(: first-line (-> string (Result Exception string))) +(defun (first-line text) + (try (with-open ((p (open-input-string text))) + (read-line p)) + #:catch (System.IO.IOException))) + +(first-line "first\nsecond") ;; (Ok "first") +}| + +@subsubsection{panic!} + +When something happens that should be impossible, @code{(panic! "message")} prints the message and exits the program. It can stand where any type is expected, which makes it useful as the last arm of a match that you know cannot be reached: + +@codeblock[#:lang "bjolang"]|{ +(match (map-try-ref deck-index card) + ((Some i) i) + (None (panic! "a card that is not in the deck"))) + +(panic! "config is broken" #:exit-code 2) +}| + +@subsection{Collections} + +Bjolang's collections are immutable by default. Adding to a vec or setting a key in a map gives you a new collection and leaves the old one untouched. That sounds expensive, but the new collection shares almost all of its structure with the old one, so an update costs about as much as it would in a mutable collection, and nobody ever has to wonder who else is holding on to the old version. + +@read-table{ +| Type | Literal | What it is | +|---------------------------+-------------------------------------+-----------------------------------------------------| +| @code{(List %a)} | @code{'(1 2 3)} @code{(list 1 2 3)} | a linked list, cheap at the front | +| @code{(Vec %a)} | @code{[1 2 3]} | an indexable vector (an RRB tree), cheap everywhere | +| @code{(Array %a)} | @code{#[1 2 3]} | a mutable .NET array of fixed length | +| @code{(Map %k %v)} | @code{#map(("a" 1) ("b" 2))} | a hash map (a CHAMP trie) | +| @code{(Set %a)} | | a hash set, in @code{(std set)} | +| @code{(OrderedMap %k %v)} | | a sorted map (a B-tree), in @code{(std orderedmap)} | +| @code{(OrderedSet %a)} | | a sorted set, in @code{(std orderedset)} | +} + +If you do not know which to use, use a @code{Vec}. + +@subsubsection{Lists} + +The list is the scheme list: a chain of @code{Cons} cells ending in @code{Nil}. It is cheap to add to and take from the front, and slow for everything else. Use it for things you build front to back and walk once. + +@codeblock[#:lang "bjolang"]|{ +(def xs (list 1 2 3)) +(def ys (Cons 0 xs)) ;; '(0 1 2 3), and xs is still '(1 2 3) + +(list-head ys) ;; 0 +(list-tail ys) ;; '(1 2 3) +(list-length ys) ;; 4, but it walks the list to find out +(list-map #(* & 10) xs) ;; '(10 20 30) +(list-filter even? ys) ;; '(0 2) +(list-foldl + 0 xs) ;; 6 +(list-reverse xs) ;; '(3 2 1) +(list-append xs xs) ;; '(1 2 3 1 2 3) +}| + +A quoted list is data: @code{'(a b c)} is a list of three symbols, not a call. Inside one, @code{,expr} splices in the value of an expression: + +@codeblock[#:lang "bjolang"]|{ +(def n 5) +'(1 2 ,n) ;; '(1 2 5) +}| + +@subsubsection{Vecs} + +@code{Vec} is the general-purpose collection. Indexing, updating at an index and adding at the end are all cheap, and it knows its own length. + +@codeblock[#:lang "bjolang"]|{ +(def v [10 20 30]) +(def v2 (vec-add v 40)) ;; [10 20 30 40] +(def v3 (vec-set v2 0 99)) ;; [99 20 30 40] +v ;; still [10 20 30] + +(vec-ref v3 0) ;; 99 +(vec-length v3) ;; 4 +(vec-filter #(> & 15) v3) ;; [99 20 30 40] +(vec-slice v3 1 2) ;; [20 30]: start at 1, take 2 +(vec-map #(+ & 1) v) ;; [11 21 31] +(vec-fold + 0 v) ;; 60 +(vec-merge v v) ;; [10 20 30 10 20 30] +}| + +@code{(list->vec xs)} and @code{(vec->list v)} convert between the two. + +@subsubsection{Arrays} + +An @code{Array} is a plain .NET array: mutable, fixed in length, and exactly what C# code expects. Use them for performance-sensitive code, or when talking to .NET. + +@codeblock[#:lang "bjolang"]|{ +(def a #[1 2 3]) +(array-set! a 0 42) ;; changes a in place +(array-ref a 0) ;; 42 +(array-length a) ;; 3 +(def zeroes (make-array 10)) +}| + +Every evaluation of an array literal makes a fresh array, so a function that returns @code{#[0 0]} returns a new one each time. + +@subsubsection{Maps} + +@code{Map} is an immutable hash map. The @code{#map(...)} literal takes entries written as @code{(key value)}, @code{[key value]} or @code{(key . value)}. + +@codeblock[#:lang "bjolang"]|{ +(def scores #map(("ada" 3) ("bo" 5))) +(def scores2 (map-set scores "cy" 1)) ;; scores still has two entries + +(map-ref scores2 "cy") ;; 1, and throws if the key is missing +(map-try-ref scores "cy") ;; None +(map-ref-or scores "cy" 0) ;; 0 +(map-contains? scores2 "bo") ;; #t +(map-length scores2) ;; 3 +(map-remove scores2 "ada") ;; a map without ada + +;; The callbacks of the map- functions get the key and the value as +;; two separate arguments. +(map-fold (fun (k v acc) (+ v acc)) 0 scores2) ;; 9 +(map-map-values #(* & 100) scores) ;; ada 300 and bo 500 +(map-filter (fun (k v) (> v 2)) scores2) ;; ada and bo, not cy +}| + +Any type with an @code{Eq} can be a key. That includes your own types, which is one reason to derive @code{Eq}. + +When a map is walked as a collection, for example in a @code{loop} or with @code{fold}, each element is a @code{(Tuple key value)} pair: + +@codeblock[#:lang "bjolang"]|{ +(loop (:for (name . score) scores) + (:do (println #"${name}: ${score}"))) +}| + +@subsubsection{Sets and ordered collections} + +Sets and the sorted collections are libraries, so they have to be imported. Their functions are named like the map functions, with their own prefix: + +@codeblock[#:lang "bjolang"]|{ +(import (std set)) +(import (std orderedmap)) + +(def s (list->set (list 3 1 4 1 5 9 2 6 5))) +(set-length s) ;; 7 +(set-contains? s 4) ;; #t +(set-contains? (set-add s 7) 7) ;; #t + +(def prices (orderedmap-set + (orderedmap-set + (orderedmap-set (orderedmap-empty) "pear" 3) + "apple" 5) + "fig" 1)) +(seq->list (orderedmap-keys prices)) ;; '("apple" "fig" "pear"): always sorted +(orderedmap-min prices) ;; (Some ("apple" . 5)) +}| + +An ordered collection sorts its keys with their @code{Ord}, so a type that derives @code{Ord} can be a key in one. Strings are sorted by code unit, not by the rules of any language, so the order is the same on every machine. + +@subsubsection{Building collections quickly} + +Updating an immutable collection one element at a time is cheap, but not free. When you build a collection from scratch in a loop, use a builder, which is a mutable collection you fill and then freeze. There are builders for lists, vecs, strings, maps and sets: + +@codeblock[#:lang "bjolang"]|{ +(def vb (vecbuilder-empty)) +(vecbuilder-add! vb "a") +(vecbuilder-add! vb "b") +(vecbuilder->vec vb) ;; ["a" "b"] + +(def lb (listbuilder-empty)) +(listbuilder-add! lb 1) +(listbuilder-add! lb 2) +(listbuilder->list lb) ;; '(1 2): builds front to back +}| + +A transient is the same thing for changing an existing map or set many times: it takes the collection, lets you mutate it, and gives you a new immutable collection back. + +@codeblock[#:lang "bjolang"]|{ +(def t (map->transientmap scores)) +(transientmap-set! t "eve" 7) +(transientmap-remove! t "ada") +(def new-scores (transientmap->map t)) ;; bo and eve; scores is untouched +}| + +In practice you rarely write either of these directly, since the accumulators of @code{loop} (next section) use builders for you. + +@subsubsection{Generic operations} + +Many operations are traits, so they work on every collection that implements them: + +@read-table{ +| Function | Works on | Does | +|-------------------------------------------------------+------------------------------------+----------------------------------------| +| @code{(length c)} @code{(empty? c)} | lists, vecs, arrays, maps, strings | size, and whether it is empty | +| @code{(ref c key)} | vecs, arrays, maps, strings | the element at an index or key; throws | +| @code{(try-ref c key)} | the same | the element as an @code{Option} | +| @code{(add c x)} @code{(add-all c xs)} | vecs, maps, sets | a new collection with more in it | +| @code{(add! b x)} @code{(add-all! b xs)} | builders and transients | put things in, in place | +| @code{(fold f init c)} | anything you can loop over | a left fold | +| @code{(map f c)} @code{(filter f c)} | the same | a lazy @code{Seq}, see below | +| @code{(any? f c)} @code{(all? f c)} @code{(find f c)} | the same | search | +} + +@codeblock[#:lang "bjolang"]|{ +(length [1 2 3]) ;; 3 +(ref [10 20 30] 1) ;; 20 +(try-ref #map((:a 1)) :b) ;; None +(add #map(("a" 1)) ("b" . 2)) ;; for a map, the element is a pair +(add-all (set-empty) [1 1 2 2 3]) ;; a set of three +(fold + 0 [1 2 3 4]) ;; 10 +(find #(> & 3) (list 1 5 7)) ;; (Some 5) + +(def b (vecbuilder-empty)) +(add-all! b (range 0 5)) +(vecbuilder->vec b) ;; [0 1 2 3 4] +}| + +@subsubsection{Mutable collections} + +For the rare cases where you really want a collection that is changed in place and shared, there are mutable versions under @code{(std mutable ...)}: @code{vec}, @code{map}, @code{set}, @code{deque} and @code{heap}. The mutable vec is a .NET @code{List}, and the mutable map is a .NET @code{Dictionary}. + +@codeblock[#:lang "bjolang"]|{ +(import (std mutable vec)) + +(def mv (mutablevec)) +(mutablevec-add! mv 3) +(mutablevec-add! mv 1) +(mutablevec-add! mv 2) +(mutablevec-sort! mv) +(mutablevec-ref mv 0) ;; 1 +}| + +Reach for these only when you have measured that you need them, or when a .NET API wants one. A mutable collection cannot be a map key, and sharing one between fibers is your own problem. + +@subsubsection{Equality} + +Equality is structural. Two collections are equal when they hold equal elements, however they were built: + +@codeblock[#:lang "bjolang"]|{ +(= [1 2 3] (vec-add [1 2] 3)) ;; #t +(= (list 1 2) (list 1 2)) ;; #t +(= #map(("a" 1)) (map-set (map-empty) "a" 1)) ;; #t +}| + +@code{eq?} asks whether two things are the very same object. You almost never want it. + +@subsubsection{Example: a deck and a score table} + +A deck is a @code{(Vec Card)}. Building it is a nested loop over suits and ranks (loops are the next section): + +@codeblock[#:lang "bjolang"]|{ +(: all-suits (Vec Suit)) +(def all-suits [Clubs Diamonds Hearts Spades]) + +(: all-ranks (Vec Rank)) +(def all-ranks [(Pip 2) (Pip 3) (Pip 4) (Pip 5) (Pip 6) (Pip 7) (Pip 8) + (Pip 9) (Pip 10) Jack Queen King Ace]) + +(: full-deck (-> (Vec Card))) +(defun (full-deck) + (loop (:for s all-suits) + (:subloop) + (:for r all-ranks) + (:acc deck (vecing (Card (rank r) (suit s)))) + => deck)) + +(vec-length (full-deck)) ;; 52 +}| + +The scores of a game are a map from player name to points. Since a map is immutable, a round of scoring is a function from the old table to a new one: + +@codeblock[#:lang "bjolang"]|{ +(: add-points (-> (Map string int) string int (Map string int))) +(defun (add-points table who points) + (map-set table who (+ points (map-ref-or table who 0)))) + +(def start #map(("ada" 0) ("bo" 0))) +(def after (add-points (add-points start "ada" 5) "cy" 2)) +(map-ref after "ada") ;; 5 +(map-ref after "cy") ;; 2: a missing player starts at 0 +(map-length start) ;; still 2 +}| + +@subsection{Loops and sequences} + +There are three ways to repeat something in bjolang. A named @code{let} (or any tail-recursive function) is the scheme way, and was covered earlier. @code{loop} is the way you will use most of the time. @code{seq} and @code{seql} make lazy sequences, which produce their elements only when someone asks for them. + +@subsubsection{loop} + +A @code{loop} is a list of clauses that run in order, once per element. @code{(:for x coll)} walks anything that can be walked: lists, vecs, arrays, maps, strings, ranges and sequences. An accumulator collects a result, and @code{=> expr} says what the loop returns: + +@codeblock[#:lang "bjolang"]|{ +(loop (:for x (list 1 2 3 4 5 6)) + (:when (even? x)) + (:acc out (listing (* x x))) + => out) +;; '(4 16 36) +}| + +The clauses are: + +@read-table{ +| Clause | Does | +|---------------------------------+---------------------------------------------------------------------| +| @code{(:for pat coll)} | binds @code{pat} to each element of @code{coll} in turn | +| @code{(:with pat start update)} | a variable that starts at @code{start} and is updated every round | +| @code{(:let pat expr)} | binds something for the rest of this round | +| @code{(:when test)} | skips the rest of this round unless @code{test} holds | +| @code{(:when-let pat expr)} | binds, and skips the round when the pattern does not match | +| @code{(:break test)} | ends the loop right away when @code{test} holds | +| @code{(:break-let pat expr)} | binds, and ends the loop when the pattern does not match | +| @code{(:final test)} | ends the loop after this round when @code{test} holds | +| @code{(:do expr ...)} | runs something for its effect | +| @code{(:acc name (collector x))} | collects @code{x} | +| @code{(:subloop)} | starts a nested loop | +| @code{=> expr} | the value of the loop (last, optional) | +} + +Since @code{:for} takes a pattern, you can take elements apart as you go, like the tuples of a map or the pairs @code{enumerate} makes: + +@codeblock[#:lang "bjolang"]|{ +(loop (:for (i . s) (enumerate ["a" "b"])) + (:acc out (listing #"${i}:${s}")) + => out) +;; '("0:a" "1:b") +}| + +@subsubsection{Accumulators} + +An accumulator names a collector and what to feed it: + +@read-table{ +| Collector | Collects | +|-----------------------------+-------------------------------------------------------------| +| @code{(listing x)} | a @code{List}, in order | +| @code{(vecing x)} | a @code{Vec} | +| @code{(mapping (k . v))} | a @code{Map}, from pairs | +| @code{(setting x)} | a @code{Set}, from @code{(std set)} | +| @code{(stringing c)} | a @code{string}, from characters | +| @code{(summing x)} | the sum of @code{x} | +| @code{(counting x)} | how many rounds reached it | +| @code{(folding init expr)} | starts at @code{init}; each round, @code{expr} is the new value | +} + +@code{folding} is the general one: inside @code{expr} the accumulator's own name is the value so far. + +@codeblock[#:lang "bjolang"]|{ +(def xs [3 9 2 7]) + +(loop (:for x xs) (:acc best (folding 0 (max best x))) => best) ;; 9 + +(loop (:for c "hello") (:acc s (stringing (char-upcase c))) => s) ;; "HELLO" + +(loop (:for k (list "a" "b")) + (:for v (list 1 2)) + (:acc m (mapping (k . v))) + => m) ;; a map from "a" to 1 and "b" to 2 +}| + +An accumulator does not need a name. Without a @code{=>}, a loop returns its accumulators, and with several of them it returns a tuple of their values in order: + +@codeblock[#:lang "bjolang"]|{ +(loop (:for x xs) (:acc (summing x)) (:acc (counting x))) ;; (21 . 4) +}| + +@subsubsection{Several collections: lockstep and nesting} + +Two @code{:for} clauses in a row walk their collections side by side, and the loop ends when the shortest one runs out. To get every combination instead, put a @code{(:subloop)} between them. Everything after it runs once for every element of the inner collection, for every element of the outer one: + +@codeblock[#:lang "bjolang"]|{ +;; side by side +(loop (:for k (list "a" "b" "c")) + (:for v (list 1 2 3)) + (:acc out (listing (k . v))) + => out) ;; '(("a" . 1) ("b" . 2) ("c" . 3)) + +;; nested +(loop (:for x (list 1 2)) + (:subloop) + (:for y (list 10 20)) + (:acc out (listing (+ x y))) + => out) ;; '(11 21 12 22) +}| + +@subsubsection{State that carries over: :with} + +@code{(:with name start update)} is a variable that starts at @code{start} and is set to @code{update} after every round. All the @code{:with} updates of a round happen at once, so each of them sees the old values of the others: + +@codeblock[#:lang "bjolang"]|{ +(loop (:with a 0 b) + (:with b 1 (+ a b)) + (:for i (range 0 10)) + (:acc out (listing a)) + => out) +;; '(0 1 1 2 3 5 8 13 21 34) +}| + +@subsubsection{Stopping early} + +@code{:break} leaves the loop before the rest of the round runs, and @code{:final} leaves it after. @code{:when-let} and @code{:break-let} bind a pattern that may fail, and skip the round or leave the loop when it does: + +@codeblock[#:lang "bjolang"]|{ +(loop (:for x (list 1 2 3 4 5)) (:break (> x 3)) (:acc out (listing x)) => out) +;; '(1 2 3) + +(loop (:for x (list 1 2 3 4 5)) (:final (> x 3)) (:acc out (listing x)) => out) +;; '(1 2 3 4) + +;; Sum the strings that are numbers, and skip the rest. +(loop (:for s (list "1" "x" "3")) + (:when-let (Ok n) (try (string->int s) #:catch (System.FormatException))) + (:acc total (summing n)) + => total) +;; 4 + +;; Number the lines of a port until it is empty. up-from without a #:to +;; never stops on its own, so the :break-let is what ends the loop. +(loop (:for line-number (up-from 1)) + (:break-let (Some line) (read-line/opt port)) + (:do (println #"${line-number}: ${line}"))) +}| + +A loop has to start with a @code{:for} or a @code{:with}, since the other clauses belong to the round that it opens. + +@subsubsection{Counting and walking part of a collection} + +@code{(:for x coll)} walks all of @code{coll} from the front. These walk something else: + +@read-table{ +| Iterator | Walks | +|-------------------------------------+----------------------------------------------------------| +| @code{(range lo hi)} | the ints from @code{lo} up to, but not including, @code{hi} | +| @code{(range-by lo hi step)} | the same, in steps of @code{step} | +| @code{(up-from n #:to t #:by b)} | counts up from @code{n}, stopping before @code{t}; without @code{#:to} it never stops | +| @code{(down-from n #:to t #:by b)} | counts down from @code{n}, stopping at @code{t} | +| @code{(in-vec v #:from f #:to t)} | part of a vec | +| @code{(in-reverse-vec v)} | a vec, last element first | +| @code{(in-array a #:from f #:to t)} | part of an array, and @code{in-reverse-array} backwards | +| @code{(over-list xs)} | the tails of a list: @code{'(1 2 3)}, @code{'(2 3)}, @code{'(3)} | +| @code{(in-port p read)} | what @code{read} returns from a port, until it returns @code{None} | +} + +@codeblock[#:lang "bjolang"]|{ +(loop (:for i (up-from 0 #:to 10 #:by 3)) (:acc out (listing i)) => out) ;; '(0 3 6 9) +(loop (:for i (down-from 5 #:to 0)) (:acc out (listing i)) => out) ;; '(5 4 3 2 1 0) +(loop (:for x (in-reverse-vec [1 2 3 4])) (:acc out (listing x)) => out) ;; '(4 3 2 1) +(loop (:for x (in-vec [1 2 3 4] #:from 1 #:to 3)) (:acc out (listing x)) => out) ;; '(2 3) +}| + +None of these allocate anything. The loop compiles to the same code you would have written by hand. + +@subsubsection{Named loops} + +A loop can be given a name, which is then bound to a function that continues with the next round. Calling it with keyword arguments overrides the value of an accumulator or a @code{:with} for the next round. It must be called in tail position: + +@codeblock[#:lang "bjolang"]|{ +(loop lp (:for x [1 2 3 4]) + (:acc total (summing x)) + (:do (if (= x 2) + (lp #:total 100) ;; reset the sum to 100 after the 2 + (lp))) + => total) +;; 107 +}| + +@subsubsection{Comprehensions} + +A comprehension is a short way to write a loop with one accumulator. It is written in braces, with the collector first, then the expression to collect, then the clauses: + +@codeblock[#:lang "bjolang"]|{ +{listing (* a a) (:for a (range 0 5))} ;; '(0 1 4 9 16) +{listing a :when (even? a) (:for a (range 0 10))} ;; '(0 2 4 6 8) +{summing x (:for x [1 2 3])} ;; 6 +{vecing (k . v) (:for k (list "a" "b")) (:for v (list 1 2))} +;; [("a" . 1) ("b" . 2)] +}| + +A loose @code{:when} right after the expression filters what is collected. Every loop clause works inside a comprehension, except @code{:acc} and @code{=>}. + +@subsubsection{Lazy sequences: seq and yield} + +A @code{(Seq %a)} is a sequence that computes its elements when they are asked for. You write one with @code{seq}, whose body calls @code{yield} for each element: + +@codeblock[#:lang "bjolang"]|{ +(: countdown (-> int (Seq int))) +(defun (countdown n) + (seq + (let loop ((i n)) + (when (> i 0) + (yield i) + (loop (- i 1)))) + (yield 0))) + +(seq->list (countdown 3)) ;; '(3 2 1 0) + +;; yield-from yields everything in another sequence. +(seq->list (seq (yield 100) (yield-from (countdown 2)) (yield 200))) +;; '(100 2 1 0 200) +}| + +Since nothing runs until it is asked for, a sequence can be endless, as long as you only ever take part of it: + +@codeblock[#:lang "bjolang"]|{ +(: naturals (-> (Seq int))) +(defun (naturals) + (seq (let loop ((i 0)) (yield i) (loop (+ i 1))))) + +(seq->list (seq-take (naturals) 5)) ;; '(0 1 2 3 4) +(seq->list (seq-take (seq-filter even? (seq-map #(* & 3) (naturals))) 4)) +;; '(0 6 12 18) +}| + +A sequence is re-run from the start every time it is walked. If computing the elements is expensive, turn it into a vec or list once with @code{seq->vec} or @code{seq->list}. + +@code{seql} is @code{loop} made lazy. It takes the same clauses, with @code{(:yield expr)} instead of an accumulator: + +@codeblock[#:lang "bjolang"]|{ +(: fibs (-> (Seq int))) +(defun (fibs) + (seql (:with a 0 b) + (:with b 1 (+ a b)) + (:yield a))) + +(seq->list (seq-take (fibs) 10)) ;; '(0 1 1 2 3 5 8 13 21 34) +}| + +The same thing as a comprehension is @code|{{seqing expr clause ...}}|. + +@subsubsection{map, filter and friends} + +The generic @code{map}, @code{filter}, @code{take}, @code{drop}, @code{take-while}, @code{drop-while}, @code{enumerate} and @code{flat-map} work on anything you can loop over, and all of them return a lazy @code{Seq}. Chains of them are fused by the compiler into a single pass. To get a concrete collection at the end, convert it: + +@codeblock[#:lang "bjolang"]|{ +(seq->list (map #(* & 2) (list 1 2 3))) ;; '(2 4 6) +(seq->vec (filter even? [1 2 3 4])) ;; [2 4] +(seq->list (take 3 (naturals))) ;; '(0 1 2) +}| + +When you want a list or a vec out directly, the collection-specific functions (@code{list-map}, @code{vec-filter} and so on) are eager and return the same kind of collection they were given. + +@subsubsection{Example: dealing and counting} + +Dealing a deck to four players means giving player 0 the cards 0, 4, 8 and so on. That is a loop over players with a loop over the hand inside it: + +@codeblock[#:lang "bjolang"]|{ +(: deal (-> (Vec Card) int int (Vec (Vec Card)))) +(defun (deal deck players per-hand) + (loop (:for p (range 0 players)) + (:acc (vecing + (loop (:for i (range 0 per-hand)) + (:acc (vecing (vec-ref deck (+ p (* i players)))))))) + => hands)) + +(: points (-> Card int)) +(defun (points c) + (match c + ((Card (rank (Pip n))) n) + ((Card (rank Ace)) 11) + (_ 10))) + +(: hand-points (-> (Vec Card) int)) +(defun (hand-points hand) + {summing (points c) (:for c hand)}) + +(def hands (deal (full-deck) 4 5)) +(vec-map hand-points hands) ;; the points of each hand +}| + +@subsection{Strings and text} + +@subsubsection{Strings and characters} + +A @code{string} is a .NET string, which means it is stored as UTF-16. A @code{char} in bjolang is not a UTF-16 unit, though: it is a whole Unicode code point. An emoji is one @code{char}, even though it takes two units in the string. That leads to two different lengths: + +@codeblock[#:lang "bjolang"]|{ +(def s "Björn 😀 åt") +(string-length s) ;; 11: storage units, O(1). Use it for sizing buffers. +(string-count s) ;; 10: characters, O(n). This is "how long is the text". +}| + +For the same reason there is no @code{string-ref} that takes an index. Finding the n:th character of a UTF-16 string means walking it, so walking is what you do: with a @code{loop}, a fold, or a cursor. + +@codeblock[#:lang "bjolang"]|{ +(loop (:for c s) (:acc n (counting c)) => n) ;; 10: loops go by character +(string->list "abc") ;; '(#\a #\b #\c) +(string-reverse "abc😀") ;; "😀cba", and the emoji survives +}| + +Characters are written @code{#\a}, @code{#\space}, @code{#\newline}. They can be compared with @code{<} and friends or @code{char=?}, converted with @code{char->int} and @code{int->char}, and classified with @code{char-alphabetic?}, @code{char-numeric?}, @code{char-whitespace?}, @code{char-upper-case?} and @code{char-lower-case?}. + +@subsubsection{Common operations} + +@codeblock[#:lang "bjolang"]|{ +(string-append "ab" "cd") ;; "abcd" +(str "a" "b" "c") ;; "abc": any number of strings +(string-upcase "björn") ;; "BJÖRN" +(string-trim " hi ") ;; "hi" +(string-pad-left "7" 3 #:with #\0) ;; "007" +(string-split "a,b,,c" ",") ;; ["a" "b" "" "c"]: a Vec +(string-join ["a" "b" "c"] ", ") ;; "a, b, c" +(string-replace "banana" "an" "AN") ;; "bANANa" +(string-contains? "banana" "nan") ;; #t +(string-starts-with? "banana" "ban") ;; #t +(string-empty? "") ;; #t +}| + +All of these compare strings ordinally, one storage unit at a time, and never according to the language settings of the machine. Upcasing and downcasing are the same everywhere too. To compare without caring about case, downcase both sides first. + +@subsubsection{Cursors} + +A cursor is a position in a string. It is as cheap as an int, but it always sits on a character boundary, and it can only be moved one character at a time, which is what makes it safe. Every cursor function takes the string as well as the cursor. + +@codeblock[#:lang "bjolang"]|{ +(: capitalise (-> string string)) +(defun (capitalise s) + (if (string-empty? s) + s + (let* ((start (string-cursor-start s)) + (rest (string-cursor-next s start))) + (string-append (char->string (char-upcase (string-cursor-ref s start))) + (substring/cursors s rest (string-cursor-end s)))))) + +(capitalise "ölstuga") ;; "Ölstuga" + +;; string-index finds the first character matching a predicate, as a cursor. +(: first-word (-> string string)) +(defun (first-word s) + (match (string-index char-whitespace? s) + ((Some c) (substring/cursors s (string-cursor-start s) c)) + (None s))) + +(first-word "hello there world") ;; "hello" +}| + +The folds walk a string for you, and are usually simpler than a cursor: + +@codeblock[#:lang "bjolang"]|{ +(: count-vowels (-> string int)) +(defun (count-vowels s) + (string-fold (fun (c n) + (if (string-contains? "aeiouy" (char->string (char-downcase c))) + (+ n 1) + n)) + 0 s)) + +(count-vowels "Hello World") ;; 3 +}| + +@subsubsection{Building strings} + +Appending strings in a loop copies the whole string every time. Use a string builder instead, or the @code{stringing} collector, which uses one for you: + +@codeblock[#:lang "bjolang"]|{ +(: join-words (-> (List string) string)) +(defun (join-words words) + (def sb (stringbuilder-empty)) + (loop (:for w words) + (:do (when (> (stringbuilder-length sb) 0) + (stringbuilder-add! sb #\space)) + (stringbuilder-add-string! sb w))) + (stringbuilder->string sb)) +}| + +Do not build strings by writing to a string port. Writing to a port is I/O as far as the compiler is concerned, and it will make every function that calls yours potentially asynchronous. + +@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: + +@codeblock[#:lang "bjolang"]|{ +(int->string 42) ;; "42" +(double->string 2.5) ;; "2.5" +(->str 3.0) ;; "3" +(string->int "42") ;; 42, and throws on anything that is not a number +(string->double "1.5") ;; 1.5 +(char->string #\a) ;; "a" +(string->symbol "hi") ;; 'hi +(keyword->string :hi) ;; "hi" +}| + +These all use the invariant culture: a number written on one machine reads back on any other, and @code{1.5} is never written as @code{1,5}. + +@subsubsection{Formatting with (std fmt)} + +For output where the layout matters, @code{(std fmt)} has two layers. The simple one prints a vec of mixed values: + +@codeblock[#:lang "bjolang"]|{ +(import (std fmt)) + +(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: + +@codeblock[#:lang "bjolang"]|{ +(show ["[" (padded/left 6 ["foo"]) "]" nl]) ;; [ foo] +(show ["[" (padded/right 6 ["foo"]) "]" nl]) ;; [foo ] +(show [(numeric/comma 1234567.891) nl]) ;; 1,234,567.891 +(show [(numeric 3.14159 #:precision 2) nl]) ;; 3.14 +(render->string [(trimmed/right 5 ["abcdefgh"])]) ;; "abcde" + +(show [(joined/last (fun (s) (each [s])) + (fun (s) (each ["and " s])) + (list "a" "b" "c") + [", "]) + nl]) ;; a, b, and c + +;; tabular takes columns, and sizes each to its content. +(show [(tabular [["name" nl "ada" nl "bo"] + ["score" nl 12 nl 7]] + #:sep " ")]) +;; name score +;; ada 12 +;; bo 7 +}| + +The full set of combinators, paragraphs and settings is described in the reference for @code{(std fmt)}. + +@subsubsection{Regular expressions with (std rx)} + +Regular expressions are written as s-expressions with the @code{#rx(...)} hash macro, not as strings. There is no escaping, and a broken pattern is a compile error rather than a runtime one: + +@codeblock[#:lang "bjolang"]|{ +(import (std rx)) + +(def date-rx #rx(seq :bos + (=> :year (= 4 :ascii-digit)) + "-" (=> :month (= 2 :ascii-digit)) + "-" (=> :day (= 2 :ascii-digit)) + :eos)) +(def number-rx #rx((+ :ascii-digit))) + +(rx-match? number-rx "123") ;; #t: the whole string +(rx-search? number-rx "abc 123") ;; #t: anywhere +(rx-split #rx((+ :space)) "a b c") ;; ["a" "b" "c"] +(rx-replace number-rx "a1b22c" "#") ;; "a#b#c" +(vec-map rx-text (rx-matches number-rx "a1b22c333")) ;; ["1" "22" "333"] +}| + +The most useful building blocks are @code{(seq ...)}, @code{(or ...)}, @code{(* r)}, @code{(+ r)}, @code{(? r)}, @code{(= n r)}, @code{(** min max r)}, @code{(=> :name r)} to capture, @code{:bos} and @code{:eos} for the ends of the string, and character classes like @code{:digit}, @code{:alpha}, @code{:space} and @code{(in "abc")}. Note that @code{:digit} is every digit in Unicode; use @code{:ascii-digit} for 0 to 9. + +The @code{Rx} and @code{RxIn} patterns use a regex inside a @code{match}. @code{Rx} matches the whole string and @code{RxIn} searches in it: + +@codeblock[#:lang "bjolang"]|{ +(: year-of (-> string string)) +(defun (year-of s) + (match s + ((Rx date-rx (Some m)) (option-value (rx-group m :year) "?")) + ((RxIn number-rx (Some m)) #"some number: ${(rx-text m)}") + (_ "no date"))) + +(year-of "2026-09-30") ;; "2026" +(year-of "room 101") ;; "some number: 101" +}| + +Define patterns at the top level, as above, rather than writing @code{#rx(...)} inside a clause. Otherwise the regex is recompiled at every call, which will tank performance. + +@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}. + +@subsubsection{Declaring and implementing a trait} + +@code{def/trait} declares a trait. It names a type variable, the implementor, and gives the signatures of its methods. @code{impl} implements it for one type: + +@codeblock[#:lang "bjolang"]|{ +(def/trait (Describe %a) + (: describe (-> %a string))) + +(impl (Describe Suit) + (defun (describe s) + (match s + (Clubs "clubs") (Diamonds "diamonds") (Hearts "hearts") (Spades "spades")))) + +(impl (Describe Rank) + (defun (describe r) + (match r + ((Pip n) (int->string n)) + (Jack "jack") (Queen "queen") (King "king") (Ace "ace")))) + +(impl (Describe Card) + (defun (describe c) + ;; These two calls go to the Rank and the Suit implementations. + #"the ${(describe (record-ref c rank))} of ${(describe (record-ref c suit))}")) + +(describe (Card (rank Queen) (suit Hearts))) ;; "the queen of hearts" +}| + +A method is called like any other function. Which implementation runs is decided by the type of the argument, at compile time when the type is known there. + +@subsubsection{Default methods} + +A trait may give a method a body. An implementation then gets that method for free, but may still write its own: + +@codeblock[#:lang "bjolang"]|{ +(def/trait (Describe %a) + (: describe (-> %a string)) + (: shout (-> %a string)) + (defun (shout x) (string-upcase (describe x)))) + +(impl (Describe Suit) ;; shout comes from the trait + (defun (describe s) + (match s + (Clubs "clubs") (Diamonds "diamonds") (Hearts "hearts") (Spades "spades")))) + +(impl (Describe Card) ;; this one writes its own shout + (defun (describe c) + #"the ${(describe (record-ref c rank))} of ${(describe (record-ref c suit))}") + (defun (shout c) "A CARD!")) + +(shout Spades) ;; "SPADES" +(shout (Card (rank Queen) (suit Hearts))) ;; "A CARD!" +}| + +@subsubsection{Constraints} + +A generic function that calls a trait method has to say that its type variable implements the trait. That is written as a @code{where} clause at the end of the signature: + +@codeblock[#:lang "bjolang"]|{ +(: describe-all (-> (Vec %a) (Vec string)) (where (Describe %a))) +(defun (describe-all xs) (vec-map describe xs)) + +(describe-all [Ace (Pip 3)]) ;; ["ace" "3"] + +(: highest (-> (List %a) (Option %a)) (where (Ord %a))) +(defun (highest xs) (list-max xs)) +}| + +@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. + +@subsubsection{Associated types} + +A trait has exactly one implementor type. When a method needs another type that depends on the implementor, such as the element type of a collection, the trait declares an associated type with @code{(type %name)}, and each implementation says what it is: + +@codeblock[#:lang "bjolang"]|{ +(def/trait (Container %c) + (type %item) + (: first-item (-> %c (Option %item)))) + +(impl (Container (List %a)) + (type %item %a) + (defun (first-item xs) + (match xs + (() None) + ((Cons x _) (Some x))))) + +(impl (Container string) + (type %item char) + (defun (first-item s) + (if (string-empty? s) + None + (Some (string-cursor-ref s (string-cursor-start s)))))) + +(first-item (list 1 2)) ;; (Some 1) +(first-item "xyz") ;; (Some #\x) +}| + +This is how the prelude's own @code{Iterable}, @code{Collection} and @code{Refable} work, and why @code{length} and @code{fold} work on so many types. + +@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: + +@codeblock[#:lang "bjolang"]|{ +(impl (Describe (List %a)) + (where (Describe %a)) + (defun (describe xs) + (string-join (list->vec (list-map describe xs)) ", "))) + +(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. Only the module that declares a trait may write its blanket: + +@codeblock[#:lang "bjolang"]|{ +(def/trait (Weight %a) + (: weight (-> %a int))) + +(impl (Weight %a) ;; everything weighs 1 ... + (defun (weight x) 1)) + +(impl (Weight string) ;; ... except strings + (defun (weight s) (string-count s))) + +(weight 42) ;; 1 +(weight "hello") ;; 5 +}| + +@subsubsection{Eq, Ord and ->str} + +Three 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 | +} + +@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. + +@codeblock[#:lang "bjolang"]|{ +(impl (Eq Card) + (defun (= a b) + (and (= (record-ref a rank) (record-ref b rank)) + (= (record-ref a suit) (record-ref b suit)))) + (defun (eq-hash c) + (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. + +@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 ->str 1) + (dyn ->str "two") + (dyn ->str (Card (rank Queen) (suit Hearts))))) +;; '("1" "two" "Q♥") +}| + +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} + +Some traits are not implemented in bjolang at all, but stand for a .NET interface, which the type either implements or does not. The numeric traits @code{Num}, @code{Integral} and @code{Ordered} are like that, and are described in the next section. Such a trait is declared with @code{#:clr-constraint}, and each method names the interface member it stands for: + +@codeblock[#:lang "bjolang"]|{ +(def/trait (Float %a) + (#:clr-constraint (System.Numerics.IFloatingPointIeee754 %a)) + (: nan? (-> %a bool) #:clr-member IsNaN) + (: finite? (-> %a bool) #:clr-member IsFinite)) + +(nan? (/ 0.0 0.0)) ;; #t +}| + +You cannot write an @code{impl} of such a trait, and no type declared in bjolang can satisfy one. + +@subsubsection{Example: cards that sort} + +Deriving @code{Ord} sorts cards by rank, but by the order the cases are declared in. If you want a different order, for example suits in bridge order, spades highest, and aces low, you write @code{compare} yourself. Here the cards are compared by rank first, with ace as 1, and by suit when the ranks are equal: + +@codeblock[#:lang "bjolang"]|{ +(type (: Card (Struct (: rank Rank) (: suit Suit)))) ;; no type/derive this time + +(: rank-value (-> Rank int)) +(defun (rank-value r) + (match r ((Pip n) n) (Ace 1) (Jack 11) (Queen 12) (King 13))) + +(: suit-value (-> Suit int)) +(defun (suit-value s) + (match s (Clubs 0) (Diamonds 1) (Hearts 2) (Spades 3))) + +(impl (Eq Card) + (defun (= a b) + (and (= (rank-value (record-ref a rank)) (rank-value (record-ref b rank))) + (= (suit-value (record-ref a suit)) (suit-value (record-ref b suit))))) + (defun (eq-hash c) + (hash-combine (rank-value (record-ref c rank)) (suit-value (record-ref c suit))))) + +(impl (Ord Card) + (defun (compare a b) + (def by-rank (compare (rank-value (record-ref a rank)) + (rank-value (record-ref b rank)))) + (if (= by-rank 0) + (compare (suit-value (record-ref a suit)) (suit-value (record-ref b suit))) + by-rank))) + +(list-sort (list (Card (rank Ace) (suit Clubs)) + (Card (rank (Pip 7)) (suit Spades)) + (Card (rank (Pip 7)) (suit Hearts)) + (Card (rank King) (suit Diamonds)))) +;; A♣ 7♥ 7♠ K♦ +}| + +Nothing about @code{list-sort} knows about cards. It asks for @code{(Ord %a)}, and now @code{Card} has one, so sorting, @code{least}, @code{list-max}, @code{less?} and ordered maps keyed by cards all work. + +@subsection{Numbers} + +@subsubsection{The types} + +There are eight numeric types, each a .NET primitive: + +@read-table{ +| Type | Range | Type | Range | +|----------------+------------------------------+----------------+------------------------| +| @code{byte} | 0 … 255 | @code{uint} | 0 … 2^32−1 | +| @code{short} | −32 768 … 32 767 | @code{long} | −2^63 … 2^63−1 | +| @code{ushort} | 0 … 65 535 | @code{ulong} | 0 … 2^64−1 | +| @code{int} | about ±2.1 billion | @code{double} | 64-bit floating point | +} + +There is no @code{float} and no @code{decimal}. A @code{char} is not a number: it can be compared and converted, but not added. + +@subsubsection{Writing numbers} + +A number with a suffix is of that type. A decimal point or an exponent makes it a @code{double}: + +@read-table{ +| Written | Type | +|----------------------------------+---------------------------------------| +| @code{21uy} | @code{byte} | +| @code{21s} | @code{short} | +| @code{21us} | @code{ushort} | +| @code{21u} | @code{uint} | +| @code{21L} | @code{long} | +| @code{21UL} | @code{ulong} | +| @code{21d} @code{2.5} @code{1e3} | @code{double} | +| @code{0x2A} @code{0b101010} | 42, of whatever type the context says | +} + +A number without a suffix has no type of its own. It takes the type of wherever it is used, and is an @code{int} only when nothing says otherwise: + +@codeblock[#:lang "bjolang"]|{ +(: doubled (-> ushort ushort)) +(defun (doubled x) (* x 2)) ;; the 2 is a ushort + +(doubled 21) ;; 42, and 21 is a ushort too + +(let ((start 5)) + (+ start 3L)) ;; 8L: start was a long all along +}| + +A literal that does not fit the type it ends up with is a compile error. @code{(doubled 99999)} says that 99999 does not fit in a @code{ushort}. + +@subsubsection{Arithmetic} + +@code{+ - * / %} are the C# operators and do what they do in C#. With more than two arguments they fold, so @code{(+ 1 2 3)} is 6. @code{(- x)} negates. + +@codeblock[#:lang "bjolang"]|{ +(/ 7 2) ;; 3: integer division truncates +(/ 7.0 2.0) ;; 3.5 +(% -7 2) ;; -1: the remainder takes the sign of the dividend +(% 7 -2) ;; 1 +}| + +Integer arithmetic wraps around on overflow, silently, as it does in C#: + +@codeblock[#:lang "bjolang"]|{ +(: bump (-> int int)) +(defun (bump n) (+ n 1)) + +(bump 2147483647) ;; -2147483648 +}| + +Integer division by zero throws @code{System.DivideByZeroException}. Dividing a double by zero gives infinity, and @code{(/ 0.0 0.0)} is NaN. The bit operations are @code{bitwise-and}, @code{bitwise-or}, @code{bitwise-xor}, @code{shift-left}, @code{shift-right} and @code{shift-right-logical}. + +@subsubsection{Converting} + +There is no automatic conversion between numeric types. Multiplying an @code{int} variable by @code{1.5} is a type error: + +@codeblock[#:lang "bjolang"]|{ +(: scale (-> int double)) +(defun (scale n) (* n 1.5)) +;; Type error: these types do not match. +;; int +;; double +}| + +@code{cast} converts, with the target written as a .NET type. It behaves like a C# cast: converting a double to an integer rounds toward zero, and narrowing an integer wraps. + +@codeblock[#:lang "bjolang"]|{ +(: scale (-> int double)) +(defun (scale n) (* (cast System.Double n) 1.5)) + +(: average (-> (Vec int) double)) +(defun (average xs) + (/ (cast System.Double (fold + 0 xs)) + (cast System.Double (vec-length xs)))) + +(cast System.Int32 2.7) ;; 2 +(cast System.Int32 -2.7) ;; -2 +(cast System.Int64 5) ;; 5L +}| + +Converting to and from strings was covered in the section on text. Remember that @code{string->int} throws on bad input, so input that is not yours should go through @code{try}: + +@codeblock[#:lang "bjolang"]|{ +(: parse (-> string (Result System.Exception int))) +(defun (parse s) (try (string->int s) #:catch (System.FormatException))) +}| + +@subsubsection{Comparing} + +There are two ways to compare, and they are for different things: + +@read-list{ +- @code{<}, @code{>}, @code{<=}, @code{>=} are the C# operators. They work on numbers and characters, and nothing else. +- @code{compare}, and everything built on it (@code{less?}, @code{greater?}, @code{at-most?}, @code{at-least?}, @code{least}, @code{greatest}, @code{list-sort}, @code{list-min}, @code{list-max}), works on every type with an @code{Ord}: numbers, strings, booleans and your own types. +} + +@codeblock[#:lang "bjolang"]|{ +(compare 2.5 1.5) ;; 1 +(least "pear" "apple") ;; "apple" +(list-sort (list 5 3 9)) ;; '(3 5 9) +(min 3 7) ;; 3, but only for numbers +(least 3 7) ;; 3, for anything with an Ord +}| + +@subsubsection{Generic numeric code} + +A function that does arithmetic on a type variable needs a constraint that says the type is a number: + +@read-table{ +| Constraint | Allows | Holds for | +|-------------------+---------------------------------+------------------------------| +| @code{Num} | @code{+ - * / %}, numeric literals | every numeric type | +| @code{Ordered} | @code{< > <= >=} | numbers and @code{char} | +| @code{Integral} | the bit operations | the integer types | +} + +@codeblock[#:lang "bjolang"]|{ +(: sum-all (-> (Vec %a) %a) (where (Num %a))) +(defun (sum-all xs) (fold + 0 xs)) + +(sum-all [1 2 3]) ;; 6 +(sum-all [1.5 2.5]) ;; 4.0 + +(: low-bit (-> %a %a) (where (Integral %a) (Num %a))) +(defun (low-bit x) (bitwise-and x 1)) + +(low-bit 7) ;; 1 +(low-bit 8L) ;; 0L +}| + +Unlike @code{Eq}, these constraints are never added for you. They are .NET interfaces, and no type you declare can ever implement them, so they say what a type is rather than what it can do, and bjolang wants that written down. The compiler tells you exactly what is missing, though: + +@codeblock[#:lang "text"]|{ +Type Error at nums.bjo:2: 'low-bit' needs (Num %a), which its signature does not declare. [...] Write: + (: low-bit ... (where (Integral %a) (Num %a))) +}| + +If a function only compares values, use @code{(where (Ord %a))} instead. It works for strings and your own types too. + +@subsubsection{The maths functions} + +@code{abs}, @code{sign}, @code{min}, @code{max}, @code{clamp}, @code{zero?}, @code{even?}, @code{odd?} and @code{sqrt} are in the prelude. The rest of @code{System.Math} is in @code{(std maths)}, and works on doubles: + +@codeblock[#:lang "bjolang"]|{ +(import (std maths)) + +(sqrt 9) ;; 3.0: the int 9 widens to a double at the call +(pow 2.0 10.0) ;; 1024.0 +(floor -2.5) ;; -3.0 +(truncate -2.5) ;; -2.0 +(round 2.5) ;; 2.0: banker's rounding, to the nearest even +(round 3.5) ;; 4.0 +(clamp 5 1 3) ;; 3 +(abs -5) ;; 5 +}| + +The logarithms are @code{loge} (natural), @code{log2}, @code{log10} and @code{(log-base x b)}. The name @code{log} is taken by the logging function of the prelude. + + diff --git a/_02_.Writing-programs.sz b/_02_.Writing-programs.sz new file mode 100644 index 0000000..da577eb --- /dev/null +++ b/_02_.Writing-programs.sz @@ -0,0 +1,1275 @@ + +@section{Writing programs} + +The first part was about the language itself, one file at a time. This part is about everything around it: splitting a program into modules, making it a project that can depend on other people's code, reading and writing files, doing several things at once, talking to .NET, extending the language with macros, and testing what you wrote. + +The card game comes along. By the end of this part it is a project with a deck module, a high-score file, players that play concurrently, and a @code{#card(Q hearts)} literal. + +@subsection{Modules} + +Every @code{.bjo} file is a module. A module decides what other modules get to see by exporting it, and everything it does not export stays private. That is the whole model: there are no namespaces inside a file, and no module declaration at the top. The file name is the module name. + +@subsubsection{Splitting the game into files} + +Here is the deck from the first part as a module of its own. Put it in @code{deck.bjo}: + +@codeblock[#:lang "bjolang"]|{ +(import (std random)) + +(export Suit Rank Card full-deck shuffle card-points) + +(type/derive (Eq Ord) + (: Suit (Union Clubs Diamonds Hearts Spades)) + (: Rank (Union (: Pip int) Jack Queen King Ace)) + (: Card (Struct (: rank Rank) (: suit Suit)))) + +(impl (->str Suit) + (defun (->str s) + (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 (->str Card) + (defun (->str c) #"${(record-ref c rank)}${(record-ref c suit)}")) + +;; Not exported: only this module needs them. +(def all-suits [Clubs Diamonds Hearts Spades]) +(def all-ranks [(Pip 2) (Pip 3) (Pip 4) (Pip 5) (Pip 6) (Pip 7) (Pip 8) + (Pip 9) (Pip 10) Jack Queen King Ace]) + +(: full-deck (-> (Vec Card))) +(defun (full-deck) + (loop (:for s all-suits) + (:subloop) + (:for r all-ranks) + (:acc deck (vecing (Card (rank r) (suit s)))) + => deck)) + +(: shuffle (-> (Vec Card) (Vec Card))) +(defun (shuffle deck) (shuffle-vec deck)) + +(: card-points (-> Card int)) +(defun (card-points c) + (match c + ((Card (rank (Pip n))) n) + ((Card (rank Ace)) 11) + (_ 10))) +}| + +And the game, in @code{game.bjo} next to it: + +@codeblock[#:lang "bjolang"]|{ +(import "deck.bjo") + +(defun (main) + (def hand (vec-slice (shuffle (full-deck)) 0 5)) + (println #"Your hand: ${hand}") + (println #"Points: ${(vec-fold (fun (c acc) (+ acc (card-points c))) 0 hand)}") + 0) +}| + +@code{bjo run game.bjo} prints something like @code{Your hand: [3♥ 10♠ 8♥ 10♦ 7♣]}. A few things to notice: + +@read-list{ +- @code{(import "deck.bjo")} is a path, relative to the file that imports it. The imported file is compiled to @code{deck.dll} first, and rebuilt whenever it (or anything it includes) changes. +- Exporting a type exports all of it: the union's cases, the struct's constructor and its fields. @code{game.bjo} can write @code{(Card (rank Ace) (suit Spades))} and match on it. +- The @code{->str} implementations were not in the export list, and still work in @code{game.bjo}. An @code{impl} has no name, so it cannot be exported. It travels with the module that wrote it. +- @code{all-suits} was not exported, so @code{game.bjo} cannot see it. +} + +Every exported binding needs a signature in the module that defines it, since the signature is what gets written into the compiled @code{.dll} for other modules to read: + +@codeblock[#:lang "text"]|{ +Export Error: Exported item 'helper' is missing a mandatory type signature at bad1.bjo:1 +}| + +@subsubsection{The standard library, and the prelude} + +A module path in a list, like @code{(std random)}, names a module of the standard library. These are found relative to the compiler installation, not the current directory, so @code{(std random)} means the same file wherever you run @code{bjo} from. Some of them: + +@read-table{ +| Module | What it has | +|---------------------------------+------------------------------------------------------| +| @code{(std random)} | random numbers, @code{shuffle-vec} | +| @code{(std set)} | sets; @code{(std orderedset)}, @code{(std orderedmap)} | +| @code{(std ports)} | reading a whole port: @code{port->lines} and friends | +| @code{(std fmt)} | text layout | +| @code{(std rx)} | regular expressions | +| @code{(std run)} | running other programs | +| @code{(std effect)} | effect handlers | +| @code{(std simpletest)} | tests | +| @code{(std syntax-match)} | writing macros | +| @code{(text json)} | JSON | +} + +The prelude, @code{(std prelude)}, is imported into every module without asking. That is where @code{println}, @code{list-map} and nearly everything in the first part comes from. If you want to get rid of a name from it, import it yourself with a modifier (modifiers are below), and the implicit import is dropped: + +@codeblock[#:lang "bjolang"]|{ +(import (except (std prelude) list-map)) +}| + +@subsubsection{Exporting types} + +A type is only visible to an importer if it is in the export list. A module cannot export a function whose signature mentions a type it keeps to itself, since the importer would have no way to read that signature. That is an error where the library is built, not where it is used. + +Sometimes you want importers to be able to hold on to a value without being able to look inside it. That is what @code{#:opaque} is for. Here is a pile of cards, in @code{pile.bjo}, that you can only draw from the top: + +@codeblock[#:lang "bjolang"]|{ +(import "deck.bjo") +(export Pile new-pile draw pile-size) + +;; Importers can hold a Pile, but only this module can look inside. +(type (: Pile #:opaque (Record (: cards (Vec Card))))) + +(: new-pile (-> Pile)) +(defun (new-pile) (Pile (cards (shuffle (full-deck))))) + +(: pile-size (-> Pile int)) +(defun (pile-size p) (vec-length (record-ref p cards))) + +;; The top card and the rest of the pile, or None when it is empty. +(: draw (-> Pile (Option (Tuple Card Pile)))) +(defun (draw p) + (match (record-ref p cards) + ([] None) + ([top rest ...] (Some (Tuple top (Pile (cards rest))))))) +}| + +Another module can call @code{new-pile}, @code{draw} and @code{pile-size}, and write @code{Pile} in its signatures, but it cannot build one with @code{(Pile ...)}, match on it, or read its fields: + +@codeblock[#:lang "text"]|{ +Type Error at game3.bjo:4: 'cards' cannot be read here. pile/Pile is exported #:opaque, +so its representation is visible only to the code of pile. A value of it can be held and +passed on here, and built and taken apart through the functions that module exports. +}| + +The @code{impl}s of an opaque type still work everywhere, so if @code{pile.bjo} had an @code{(impl (->str Pile) ...)}, printing a pile would work in any module. + +@subsubsection{Import modifiers} + +An import can be wrapped in modifiers that decide what the names it brings in are called, and which of them arrive: + +@read-table{ +| Modifier | Does | +|---------------------------------+-------------------------------------------------------| +| @code{(only m a b ...)} | only these functions and macros | +| @code{(except m a b ...)} | everything but these | +| @code{(prefix m "p/")} | puts @code{p/} in front of every name | +| @code{(postfix m "/p")} | the same, at the end | +| @code{(prefix-defs m "p/")} | a prefix on functions and macros only | +| @code{(prefix-types m "P/")} | a prefix on types, constructors and traits only | +| @code{(rename m (old new) ...)} | renames functions and macros | +} + +@codeblock[#:lang "bjolang"]|{ +(import "deck.bjo" + (prefix "pile.bjo" "pile/")) + +(defun (main) + (match (pile/draw (pile/new-pile)) + ((Some (Tuple c rest)) (println #"Drew ${c}, ${(pile/pile-size rest)} left")) + (None (println "empty"))) + 0) +}| + +Modifiers nest, and are read inside out: @code{(prefix (except (std set) set-map) "s/")} drops @code{set-map} and prefixes the rest. + +@code{only} and @code{except} work on functions and macros, never on types. The types a module exports always arrive, because the signatures of the functions that do arrive might need them: + +@codeblock[#:lang "bjolang"]|{ +(import (only "deck.bjo" full-deck) + (rename (std random) (shuffle-vec mix))) + +(defun (main) + (println (vec-length (mix (full-deck)))) ;; 52 + (println (Card (rank Ace) (suit Spades))) ;; A♠, Card came along anyway + 0) +}| + +A type belongs to the module that declared it. If two libraries both declare a @code{Card}, those are two different types, and the bare name @code{Card} means the one from the later import. @code{prefix-types} gives each of them a name of its own: + +@codeblock[#:lang "bjolang"]|{ +(import (prefix-types "poker.bjo" "P/") + (prefix-types "bridge.bjo" "B/")) + +(: convert (-> P/Card B/Card)) +}| + +@subsubsection{Which name wins} + +When two things have the same name, three rules decide, in this order: + +@read-list{ +- A name defined in the module itself wins over anything imported. +- Between two imports, the later one wins, silently. This is what lets any module override the prelude: the prelude is always the first import. +- A name that a modifier or an alias made up may not collide with anything else. That is an error, since you asked for that name by hand and two answers mean the request was ambiguous. +} + +@subsubsection{re-export and :alias} + +@code{export} publishes what a module defines. @code{re-export} publishes something the module imported, which is how you write a module that gathers several others behind one import. The prelude passes on @code{=}, @code{compare} and @code{list-sort} from @code{(std eq)} this way. For the game: + +@codeblock[#:lang "bjolang"]|{ +;; cards.bjo: one import for everything about cards +(import "deck.bjo" "pile.bjo") + +(re-export Card Rank Suit full-deck shuffle Pile new-pile draw) +}| + +A re-exported type is the same type, not a copy, so a @code{Card} made through @code{cards.bjo} is a @code{Card} to @code{deck.bjo} as well. The @code{impl}s do not come along though: they travel with the module that wrote them, so a program that wants to print cards has to import @code{deck.bjo} too. + +@code{(:alias new old)} gives a binding or a macro a second name. Together with a re-export or an export, that is how a library publishes something under a nicer name than it was written under: + +@codeblock[#:lang "bjolang"]|{ +(:alias deal full-deck) +(export deal) +}| + +@subsubsection{include} + +@code{(include "file.bjo")} pastes the file's forms in where the include is, as if you had written them there. No module is made, nothing needs exporting, and everything in the included file is in scope directly: + +@codeblock[#:lang "bjolang"]|{ +;; helpers.bjo +(: double (-> int int)) +(defun (double x) (* x 2)) +}| + +@codeblock[#:lang "bjolang"]|{ +(include "helpers.bjo") + +(defun (main) + (println (double 21)) ;; 42 + 0) +}| + +The rule of thumb: use @code{include} to split one module that has grown too big across several files, and @code{import} when the other file is a module in its own right, with its own idea of what is public. + +@subsection{Projects and packages} + +So far every program has been a file, and the files it imports are found by path. That works fine for small things. For anything that depends on someone else's code, or that you want others to depend on, make it a project. + +A project is a directory with a @code{manifest.bjodat} in it. Inside a project @code{bjo} knows what your code is called, what it needs and where to get it. Outside one, every command works on the file you give it, exactly like before. + +@subsubsection{Making a project} + +@codeblock[#:lang "text"]|{ +mkdir cards && cd cards +bjo init +bjo run +}| + +@code{bjo init} makes this: + +@codeblock[#:lang "text"]|{ +cards/ + manifest.bjodat what the package is called, and what it needs + src/ + main.bjo the program + tests/ + .gitignore +}| + +and the manifest says what the package is called: + +@codeblock[#:lang "bjolang"]|{ +(package + (name (cards)) + (version "0.1.0")) +}| + +All the modules of a package live in @code{src/}, and a module's name is its path below it. So if you move @code{deck.bjo} into @code{src/}, it becomes the module @code{(cards deck)}, and @code{src/main.bjo} imports it like any library module: + +@codeblock[#:lang "bjolang"]|{ +(import (cards deck)) + +(defun (main args) + (println #"Your hand: ${(vec-slice (shuffle (full-deck)) 0 5)}") + (println #"Arguments: ${args}") + 0) +}| + +@code{bjo} finds the project by looking upwards for a manifest, so every command works from anywhere inside it. + +@read-table{ +| Command | What it does | +|---------------------------+----------------------------------------------------------------| +| @code{bjo init [--lib]} | makes a project in the current directory | +| @code{bjo fetch} | fetches the dependencies, compiles nothing | +| @code{bjo build} | fetches, then compiles the program, or every module of a library | +| @code{bjo run . args ...} | builds, then runs the program with the arguments | +| @code{bjo check} | reports every error in the project, and builds nothing | +| @code{bjo repl} | the REPL, where the project's modules can be imported | +} + +The @code{.} in @code{bjo run . one two} stands for "the project". Everything after the file (or the dot) is passed to your program, so here @code{args} is @code{[one two]}. A file named inside a project, like @code{bjo run tests/deck-test.bjo}, is compiled against the project's packages, which is how you run tests. + +@subsubsection{Libraries} + +A package whose entry file, @code{src/main.bjo}, does not exist is a library. @code{bjo init --lib} makes one, and @code{bjo build} in it compiles every module under @code{src/}. + +So let us make the deck a library of its own, in a directory next to the game: + +@codeblock[#:lang "text"]|{ +mkdir cardlib && cd cardlib +bjo init --lib +mv ../cards/src/deck.bjo src/ +}| + +The game then depends on it by path, in @code{cards/manifest.bjodat}, and imports it as @code{(cardlib deck)}: + +@codeblock[#:lang "bjolang"]|{ +(package + (name (cards)) + (version "0.1.0") + (depends + (package (name (cardlib)) + (source (path (dir "../cardlib")))))) +}| + +@codeblock[#:lang "bjolang"]|{ +(import (cardlib deck)) + +(defun (main) + (println (vec-slice (shuffle (full-deck)) 0 5)) + 0) +}| + +A path is relative to the manifest that names it. A path dependency has no version: what is in the directory is what you get, which is what you want while you are working on both. + +@subsubsection{Dependencies from git} + +When the library is published somewhere, depend on it through git instead: + +@codeblock[#:lang "bjolang"]|{ +(depends + (package (name (cardlib)) + (version (version-at-least "0.1.0")) + (source (git (url "https://github.com/someone/cardlib"))))) +}| + +A version is a git tag of exactly the form @code{vX.Y.Z}, and a git dependency must say which versions it takes. There are three ways to say it: @code{(version-at-least "1.2")}, @code{(version-between "1.2" "2.0")} and @code{(version-at-most "2.0")}. + +Versions are chosen by minimal version selection, which is simpler than it sounds. Every package in the graph says the lowest version it needs of each of its dependencies, and the version picked is the highest of those. Not the newest version that exists, but the lowest one that everyone who asked can live with. This means that a new release of a dependency changes nothing in your build until some manifest asks for it, and that the same manifests build the same code a year from now. An upper bound is only ever checked, never used to pick an older version. + +@code{bjo fetch} (and @code{build} and @code{run}, which fetch first) clones what is needed into @code{.bjo/} and writes @code{bjo.lock}, with the exact commit each dependency resolved to. Commit the lock file, and do not commit @code{.bjo/} (the generated @code{.gitignore} already leaves it out). If a tag is moved to a different commit after it was locked, the build stops rather than quietly using different code under the same version. @code{--locked} turns any difference from the lock file into an error, which is what you want on a build server. + +If your own manifest gives a dependency a source, that source is used everywhere in the graph, including for the dependencies of your dependencies. That is how you swap in a fork, or a path to a local checkout while you debug something. + +@subsubsection{NuGet packages} + +.NET libraries from NuGet are listed under @code{packages}: + +@codeblock[#:lang "bjolang"]|{ +(package + (name (shop)) + (version "0.1.0") + (packages (nuget (id "Npgsql") (version "9.0.3")))) +}| + +Their types are then used like any other .NET type (see the .NET section below). The version means what NuGet says it means: @code{"9.0.3"} is that version or newer, and @code{"[9.0.3]"} is exactly that version. NuGet packages are restored by the .NET SDK, so this needs the @code{dotnet} command, and what they resolved to is recorded in @code{packages.lock.json}, which should also be committed. For now, the built program loads the packages from the NuGet cache, so it only runs on the machine that built it. + +@subsubsection{Publishing a package} + +@read-list{ +- Put the modules under @code{src/}, and write a manifest with a @code{name} and a @code{version}. +- @code{bjo build} to check that everything compiles. +- Tag the release, @code{git tag -a v0.1.0 -m "First release"}, and push the tag. A version that is not a @code{vX.Y.Z} tag cannot be depended on. +} + +@subsection{Files and I/O} + +@subsubsection{Whole files} + +When a file fits in memory, the simplest thing is to read or write all of it at once: + +@read-table{ +| Function | Does | +|-------------------------------------------+---------------------------------------------------| +| @code{(file-read-text path)} | the whole file, as a string | +| @code{(file-write-text path s)} | writes @code{s}, replacing what was there | +| @code{(file-append-text path s)} | adds @code{s} at the end, creating the file | +| @code{(file-read-lines path)} | the lines, as a @code{(Vec string)} | +| @code{(file-write-lines path lines)} | writes a @code{(Vec string)}, one line each | +| @code{(file-read-bytes path)} | the whole file, as an @code{(Array byte)} | +| @code{(file-exists? path)} | whether there is a file there | +| @code{(file-delete path)} | deletes it; a missing file is not an error | +| @code{(file-copy from to)} | copies, and fails rather than overwrite @code{to} | +| @code{(file-move from to)} | moves a file or a directory | +| @code{(file-info path)} | size, modified time and kind, as an @code{Option} | +} + +These throw when something goes wrong, the way the .NET methods under them do. Wrap a call in @code{try} when a failure is something you expect. + +Here is a high-score file for the game. Each line is a name and a number, and a line that is not one is skipped rather than crashing the game: + +@codeblock[#:lang "bjolang"]|{ +(: score-file string) +(def score-file "scores.txt") + +(: parse-score (-> string (Option (Tuple string int)))) +(defun (parse-score line) + (match (string-split line " ") + ([name points] + (match (try (string->int points) #:catch (System.FormatException)) + ((Ok n) (Some (Tuple name n))) + ((Err _) None))) + (_ None))) + +(: read-scores (-> (Vec (Tuple string int)))) +(defun (read-scores) + (if (file-exists? score-file) + (loop (:for line (file-read-lines score-file)) + (:when-let (Some score) (parse-score line)) + (:acc (vecing score))) + [])) + +(: save-score (-> string int void)) +(defun (save-score name points) + (file-append-text score-file #"${name} ${points}\n")) + +(defun (main) + (save-score "ada" 31) + (save-score "bo" 27) + (println (read-scores)) ;; [(ada, 31) (bo, 27)] + 0) +}| + +@subsubsection{Paths, directories and the environment} + +Paths are strings, and the path functions only work on the text. They never touch the disk: + +@codeblock[#:lang "bjolang"]|{ +(path-combine "a" "b" "c.txt") ;; "a/b/c.txt" +(path-file-extension "deck.bjo") ;; (Some ".bjo") +(path-filename "/tmp/x/") ;; None: it names a directory +(path-directory "/tmp/x/y.txt") ;; (Some "/tmp/x") +(path-absolute "deck.bjo") ;; the full path +}| + +The directory functions do touch the disk: + +@codeblock[#:lang "bjolang"]|{ +(directory-create "saves/2026") ;; creates every missing directory on the way +(directory-exists? "saves") ;; #t +(directory-files ".") ;; the files directly in ".", as a (Vec string) +(directory-subdirectories ".") ;; and the directories +(directory-delete-tree "saves") ;; everything under it, and it + +;; There is no pattern argument. Filter instead: +(vec-filter #(string-ends-with? & ".bjo") (directory-files ".")) + +;; Every file under a directory, lazily, without going into .git: +(filter #(string-ends-with? & ".bjo") + (directory-walk "." #:into? (fun (d) (not (= (path-filename d) (Some ".git")))))) +}| + +And the environment: + +@codeblock[#:lang "bjolang"]|{ +(get-environment-variable "HOME") ;; (Some "/home/linus"), or None +(set-environment-variable! "MODE" "x") ;; for this process and the ones it starts +(current-directory) +}| + +@subsubsection{Ports} + +For files that are too big to read at once, or for reading and writing a bit at a time, there are ports. A text input port is something you can read lines and characters from, and an output port is something you can write to. A file port and a string port are the same type, so a function that reads from a port does not care where the text comes from. + +Opening a file answers a @code{Result}, since a missing file is something you should expect. The @code{Err} holds the real .NET exception, so you can tell the failures apart with @code{:is}: + +@codeblock[#:lang "bjolang"]|{ +(match (open-input-file "nope.txt") + ((Ok port) (read-all port)) + ((Err (:is System.IO.FileNotFoundException e)) #"missing: ${(.-FileName e)}") + ((Err e) "some other error")) +}| + +The easiest way to use a port is to let @code{call-with-input-file} or @code{call-with-output-file} open it, hand it to a function, and close it however that function ends. Here is counting the lines of a file of any size: + +@codeblock[#:lang "bjolang"]|{ +(: count-lines (-> string (Result Exception int))) +(defun (count-lines path) + (call-with-input-file path + (fun (port) + (loop (:for i (up-from 0)) + (:break-let (Some line) (read-line/opt port)) + (:acc lines (counting line)) + => lines)))) + +(count-lines "scores.txt") ;; (Ok 2) +}| + +@read-table{ +| Function | Does | +|-----------------------------------+---------------------------------------------------------| +| @code{(open-input-file path)} | a @code{(Result Exception TextInputPort)} | +| @code{(open-output-file path)} | the same for writing; @code{#:mode} is @code{Truncate}, @code{Append} or @code{CreateNew} | +| @code{(read-line/opt p)} | the next line, or @code{None} at the end | +| @code{(read-char/opt p)} | the next character, or @code{None} at the end | +| @code{(read-all p)} | everything that is left, as one string | +| @code{(port-eof? p)} | whether there is nothing more to read | +| @code{(write-string p s)} | writes @code{s} | +| @code{(writeln p s)} | writes @code{s} and a newline | +| @code{(close-input-port p)} | and @code{close-output-port} | +| @code{(open-input-string s)} | a port that reads from a string | +| @code{(open-output-string)} | a port that collects what is written to it; @code{get-output-string} reads it | +} + +@code{read-line} and @code{read-char} also exist without the @code{/opt}. They throw at the end of the input. + +@code{(std ports)} has the functions that read a whole port into a collection: @code{port->lines}, @code{port->list}, @code{port->vec} and the lazy @code{port->seq} and @code{file->seq}: + +@codeblock[#:lang "bjolang"]|{ +(import (std ports)) + +(call-with-input-file "scores.txt" port->lines) ;; (Ok ["ada 31" "bo 27"]) +}| + +A file port belongs to the scope it was opened in (scopes are in the concurrency section), and is closed when that scope ends, whether you closed it or not. @code{main} is a scope, so a port you forget is closed at the latest when the program ends. For anything long-running, close ports yourself or use @code{call-with-input-file}. + +@subsubsection{Running other programs} + +@code{(std run)} runs other programs. The command is written as a quoted list, and pipes and redirections are part of the same notation: + +@codeblock[#:lang "bjolang"]|{ +(import (std run)) + +(run/string '(echo "hello")) ;; (Ok "hello\n") +(run/strings '(pipe (cat "scores.txt") (sort -r))) ;; (Ok ["bo 27" "ada 31"]) +(run/status '(into-file "sorted.txt" + (pipe (cat "scores.txt") sort))) ;; (Ok 0), the exit code + +(def who "ada") +(run/strings '(grep ,who "scores.txt")) ;; (Ok ["ada 31"]) +}| + +@code{,who} puts the value of a variable into the command. Every word is one argument, spaces and all, so there is no quoting to get wrong. @code{pipe}, @code{into-file}, @code{from-file}, @code{append-to-file} and @code{errors-into-file} are part of the notation, and their arguments are checked when the program is compiled. Anything else at the head of a form is the name of a program to run. + +@read-table{ +| Function | Answers | +|---------------------------+------------------------------------------------------| +| @code{(run/string form)} | what the command wrote to stdout | +| @code{(run/strings form)} | the same, one string per line | +| @code{(run/status form)} | the exit code | +| @code{(run/output form)} | the exit code and stdout | +| @code{(run form)} | a running @code{Proc}, whose input and output are yours | +} + +All of them answer a @code{Result}, and all of them block the thread they run on. Inside a bjoroutine there are versions that do not; see the documentation of @code{(std run)}. + +@subsection{Concurrency} + +Bjolang's concurrency is built on three ideas: lightweight threads called bjoroutines, channels to talk between them, and scopes that make sure nothing started inside them outlives them. The channel operations are Concurrent ML's, which means an operation like "receive from this channel" is a value that can be combined with others before you wait for it. + +@subsubsection{Bjoroutines} + +A function that may wait for something (a channel, a timer, a file) is defined with @code{defbjo} instead of @code{defun}. Its signature is written with @code{-bjo->}. Waiting is done with @code{sync}: + +@codeblock[#:lang "bjolang"]|{ +(: think (-bjo-> int int)) +(defbjo (think n) + (sync (timeout 50)) ;; wait 50 ms, without holding up a thread + (* n n)) +}| + +A bjoroutine runs as a fiber on a pool of threads. When it waits, it gives its thread back, so ten thousand of them waiting on a channel cost ten thousand small objects, not ten thousand threads. + +The catch is colour. Calling a bjoroutine is a place where the caller may have to wait too, so only a bjoroutine may call one: + +@codeblock[#:lang "text"]|{ +Type Error at colour.bjo:7: calling 'think' is a yield point, and a yield point is not allowed here. + 'twice' is defined with (defun ...), which is emitted as an ordinary C# method, and an ordinary + method cannot await. + Define it with (defbjo ...), or move the suspending call out of it. Note that (defbjo ...) spreads: + whoever calls 'twice' needs to be one too. +}| + +@code{main} may be a @code{defbjo}, and that is how a program gets into the world of bjoroutines. + +The colour does not spread as far as you might fear. Most of the prelude's I/O, like @code{file-read-text}, is written so that it has two copies: one that blocks and one that waits. An ordinary @code{defun} that calls one of them gets two copies too, without you writing anything, and a bjoroutine calling it gets the one that waits: + +@codeblock[#:lang "bjolang"]|{ +(: size-of (-> string int)) +(defun (size-of path) (string-length (file-read-text path))) ;; an ordinary defun + +(defbjo (main) + (println (size-of "deck.bjo")) ;; the file is read without blocking a thread + 0) +}| + +@subsubsection{spawn and channels} + +@code{(spawn (f args ...))} starts @code{f} as a new fiber and carries on. The arguments are evaluated where the @code{spawn} is written, and only the call happens in the new fiber. + +A channel, made with @code{make-chan}, is how fibers talk. @code{(chan-send ch v)} and @code{(chan-recv ch)} are events, and @code{sync} performs them. A channel is a rendezvous: a send waits until someone receives, and the other way around. + +Here are the players of the card game as bjoroutines. Each player gets a seat, which is a channel, and plays one card into it per trick, highest first. The table (@code{main}) takes one card from each seat in turn: + +@codeblock[#:lang "bjolang"]|{ +(import "deck.bjo") + +(: player (-bjo-> (Vec Card) (Chan Card) void)) +(defbjo (player hand seat) + (loop (:for c (list-reverse (list-sort (vec->list hand)))) + (:do (sync (chan-send seat c))))) + +;; One trick: a card from every seat, in turn. +(: play-trick (-bjo-> (Vec (Chan Card)) (Vec Card))) +(defbjo (play-trick seats) + (loop (:for seat seats) + (:acc (vecing (sync (chan-recv seat)))))) + +(defbjo (main) + (def deck (shuffle (full-deck))) + (def seats [(make-chan) (make-chan) (make-chan)]) + (loop (:for seat seats) + (:for p (range 0 3)) + (:do (spawn (player (vec-slice deck (* p 4) 4) seat)))) + (loop (:for trick (range 1 5)) + (:do (println #"Trick ${trick}: ${(play-trick seats)}"))) + 0) +}| + +@codeblock[#:lang "text"]|{ +Trick 1: [K♥ K♦ J♥] +Trick 2: [8♣ 7♣ 4♣] +Trick 3: [6♠ 4♥ 3♣] +Trick 4: [6♦ 3♦ 2♦] +}| + +Since each send waits for its receive, a player cannot run ahead and play two cards into the same trick. + +@subsubsection{Events are values} + +@code{(chan-recv seat)} does not receive anything. It describes a receive that has not happened yet, and only @code{sync} makes it happen. Until then it is a value like any other, and can be passed around and combined: + +@read-table{ +| Function | Is the event that | +|---------------------------+-------------------------------------------------------------| +| @code{(chan-recv ch)} | receives a value from @code{ch} | +| @code{(chan-send ch v)} | sends @code{v} on @code{ch} | +| @code{(timeout ms)} | happens after @code{ms} milliseconds | +| @code{(choose e1 e2 ...)} | happens when the first of its events happens | +| @code{(wrap e f)} | happens when @code{e} does, and answers @code{f} of its value | +} + +With those, a deadline is an ordinary function that works on any event at all. A player who takes too long forfeits: + +@codeblock[#:lang "bjolang"]|{ +(: within (-> int (Event %a) (Event (Option %a)))) +(defun (within ms ev) + (choose (wrap ev #(Some &)) + (wrap (timeout ms) (fun (u) None)))) + +(: slow-player (-bjo-> (Chan string) void)) +(defbjo (slow-player seat) + (sync (timeout 200)) + (sync (chan-send seat "7♣"))) + +(defbjo (main) + (def seat (make-chan)) + (spawn (slow-player seat)) + (println (sync (within 50 (chan-recv seat)))) ;; None + (println (sync (within 500 (chan-recv seat)))) ;; (Some 7♣) + 0) +}| + +Note that @code{within} is a @code{defun}. Building an event is not waiting for it, so no colour is involved until the @code{sync}. + +The part worth noticing is the second line. When the timeout won the first race, the receive was withdrawn, not performed, so the card was not taken off the channel and lost. It was still there for the second try. + +@subsubsection{Answers from a fiber: bjo} + +@code{spawn} throws away what the function answers. @code{bjo} instead gives you a promise of it, which you can wait for with @code{promise-join}. The answer is a @code{Result}, since the fiber might have failed: + +@codeblock[#:lang "bjolang"]|{ +(def a (bjo (think 3))) +(def b (bjo (think 4))) +(sync (promise-join a)) ;; (Ok 9) +(sync (promise-join b)) ;; (Ok 16) +}| + +Both fibers think at the same time, so this takes 50 ms and not 100. + +@subsubsection{Scopes} + +Every fiber is started inside a scope, and a scope does not end until every fiber started in it has ended. So when a scope returns, the work started inside it is over. Nothing keeps running in the background by accident, and if a fiber fails, the failure is reported when the scope ends instead of disappearing. + +@code{main} is a scope, which is why the players above did not need anything special. You make more of them with these forms: + +@read-table{ +| Form | Is a scope that | +|---------------------------------------+------------------------------------------------------------| +| @code{(with-scope body ...)} | waits for its fibers, and closes its files | +| @code{(with-cancel (cancel) body ...)} | also binds @code{cancel}, which tells its fibers to stop | +| @code{(with-deadline ms body ...)} | is cancelled after @code{ms} milliseconds | +| @code{(with-shield body ...)} | is not cancelled when its parent is; for cleaning up | +} + +@codeblock[#:lang "bjolang"]|{ +(: fetch (-bjo-> string void)) +(defbjo (fetch name) + (sync (timeout 30)) + (println #"fetched ${name}")) + +(: count-up (-bjo-> (Chan int) void)) +(defbjo (count-up ch) + (let go ((i 0)) + (sync (chan-send ch i)) + (go (+ i 1)))) + +(defbjo (main) + ;; Both are fetched when this returns, or the deadline raises. + (with-deadline 1000 + (spawn (fetch "a")) + (spawn (fetch "b"))) + (println "both done") + + ;; count-up never ends on its own. Cancelling the scope stops it + ;; the next time it waits. + (def numbers (make-chan)) + (with-cancel (cancel) + (spawn (count-up numbers)) + (println (sync (chan-recv numbers))) ;; 0 + (println (sync (chan-recv numbers))) ;; 1 + (cancel (Requested "enough"))) + 0) +}| + +Cancelling does not interrupt anything. A cancelled fiber notices the next time it waits for something, and a fiber in a loop that never waits never notices. A fiber that stopped because it was cancelled has not failed; that is how a worker normally ends. + +There are four ways to start a fiber, and they differ only in what the scope does about it: + +@read-table{ +| Form | The scope | +|------------------------------------+----------------------------------------------------------| +| @code{(spawn (f x))} | waits for it, and reports it if it fails | +| @code{(bjo (f x))} | waits for it; the promise is yours, and so is the failure | +| @code{(spawn/daemon (f x))} | cancels it when the scope ends, but does not wait for it | +| @code{(spawn/detached (f x))} | has nothing to do with it | +} + +The scope forms wait, so they can only be written in a bjoroutine. + +@subsubsection{Work that is not a fiber} + +A fiber that blocks its thread, with a .NET call that does not know about fibers, or with a long computation, holds up one of the threads all the other fibers share. There are two ways to move such work off the pool, and both give you an event: + +@codeblock[#:lang "bjolang"]|{ +;; Work that waits: a call that parks its thread. +(sync (blocking #(file-read-text "deck.bjo"))) ;; (Ok "...") + +;; Work that computes: gets a thread of its own. +(sync (spawn/thread #(loop (:for i (range 0 1000)) (:acc (summing i))))) ;; (Ok 499500) +}| + +The other direction also exists. An ordinary @code{defun} cannot @code{sync}, but it can wait for an event by parking its own thread with @code{sync/blocking}. Do not use it inside a bjoroutine, since the thread it parks is one the fibers need: + +@codeblock[#:lang "bjolang"]|{ +(: wait-for-card (-> (Chan Card) Card)) +(defun (wait-for-card ch) + (sync/blocking (chan-recv ch))) +}| + +@subsection{Effects} + +The deck is shuffled with @code{shuffle-vec}, which is random. That is what you want when playing, and exactly what you do not want in a test that checks who wins a trick. The usual answer is to pass a shuffle function down to everything that needs one. Effects are the other answer: the function performs an operation, and whoever called it decides what the operation does. + +@subsubsection{Declaring an effect} + +@codeblock[#:lang "bjolang"]|{ +(import (std effect) "deck.bjo") + +(defeffect Dealing + (shuffle-deck (-> (Vec Card) (Vec Card))) + #:default ((shuffle-deck shuffle))) + +(: deal-hand (-> int (Vec Card))) +(defun (deal-hand n) + (vec-slice (shuffle-deck (full-deck)) 0 n)) +}| + +@code{shuffle-deck} is now a function with the declared type, and @code{deal-hand} calls it like any other function. With nobody handling it, it does its default, which here is the real shuffle. A test installs another one with @code{with-handler}: + +@codeblock[#:lang "bjolang"]|{ +(deal-hand 5) ;; five random cards +(with-handler ((shuffle-deck (fun (deck) deck))) + (deal-hand 5)) ;; [2♣ 3♣ 4♣ 5♣ 6♣], every time +}| + +The handler applies to everything called inside the @code{with-handler}, however deep, and to fibers spawned inside it. Nothing had to be passed through @code{deal-hand}. + +A few things to know: + +@read-list{ +- An effect without a @code{#:default} raises an exception when it is performed and nobody handles it. Leave the default out when running without a handler is a mistake. +- A handler is called like a function, and answers once. There are no continuations: it cannot resume twice, or not at all, except by throwing. +- An operation declared with @code{->} can be performed from any function. One declared with @code{-bjo->} can only be performed from a bjoroutine, and needs a bjoroutine as its handler. +- Inside its own handler, performing an operation calls the handler again. @code{(handler-of op)} is the handler that was installed before, which is how a handler adds something and passes the rest on: +} + +@codeblock[#:lang "bjolang"]|{ +(with-handler ((log (let ((outer (handler-of log))) + (fun (s) (outer (str "[game] " s)))))) + (log "dealing")) ;; "[game] dealing" on stderr +}| + +@subsubsection{The prelude's own effects} + +Some things in the prelude are effects already, so you can handle them without changing the code that uses them: + +@read-table{ +| Operation | Default | +|----------------------------+----------------------------------------------------------| +| @code{(log s)} | writes a line to stderr | +| @code{(warn s)} | writes a line to stderr | +| @code{(getenv name)} | reads an environment variable, as an @code{Option} | +| @code{(monotonic-ms)} | a clock for measuring time | +| the @code{FS} operations | the real file system | +} + +Everything in the prelude that reaches the disk goes through the @code{FS} operations, so a test can replace the whole file system. That is @code{with-fake-fs}, which is in the testing section below. + +Collecting the log lines of a piece of code, instead of printing them, is a handler: + +@codeblock[#:lang "bjolang"]|{ +(def lines (make-box (list))) +(with-handler ((log (fun (s) (box-set! lines (Cons s (box-ref lines)))))) + (log "one") + (log "two")) +(box-ref lines) ;; '("two" "one") +}| + +A @code{with-handler} does not wait for anything, unlike a scope. If the code inside it spawns fibers that log, put the scope that waits for them inside the handler, or you will read the lines before they are written. + +@subsection{.NET} + +Bjolang compiles to C#, so every .NET library is available. You tell the compiler which methods and classes you want and what their types are, and they become ordinary bjolang functions and types. + +@subsubsection{Methods: import/extern} + +Let us give the suits colours in the terminal. @code{System.Console} has what is needed: + +@codeblock[#:lang "bjolang"]|{ +(import "deck.bjo") + +(import/class + (ConsoleColor (: System.ConsoleColor))) + +(import/extern + (set-foreground! (: System.Console.ForegroundColor (-> ConsoleColor void) #:set)) + (reset-colour! (: System.Console.ResetColor (-> void))) + (console-write (: System.Console.Write (-> string void)))) + +(: suit-colour (-> Suit ConsoleColor)) +(defun (suit-colour s) + (match s + ((or Hearts Diamonds) ConsoleColor.Red) + ((or Clubs Spades) ConsoleColor.Blue))) + +(: print-card (-> Card void)) +(defun (print-card c) + (set-foreground! (suit-colour (record-ref c suit))) + (console-write #"${c} ") + (reset-colour!)) + +(defun (main) + (vec-for-each print-card (vec-slice (shuffle (full-deck)) 0 5)) + (println "") + 0) +}| + +Each entry in @code{import/extern} is a name for bjolang, the full .NET name, and a type. Methods with many overloads, like @code{Console.Write}, are resolved from the type you give. An instance method takes the object as its first argument. @code{#:get} and @code{#:set} read and write a property or a field: + +@codeblock[#:lang "bjolang"]|{ +(import/extern + (trim (: System.String.Trim (-> string string))) + (max-int (: System.Int32.MaxValue #:get))) + +(trim " hi ") ;; "hi" +max-int ;; 2147483647 +}| + +Do not give an import the name of a prelude function. A name the module binds itself, or gets from the prelude, wins over an @code{import/extern} alias, so an alias called @code{println} would never be used. + +@subsubsection{Classes: import/class} + +@code{import/class} makes a .NET class a bjolang type, and its constructor a function spelled with a dot at the end. Enums work the same way, and their members are written @code{Type.Member}, as with @code{ConsoleColor.Red} above. + +@codeblock[#:lang "bjolang"]|{ +(import/class + (StringBuilder (: System.Text.StringBuilder (-> StringBuilder)))) + +(def sb (StringBuilder.)) ;; new StringBuilder() +(ignore (.Append sb "hello, ")) +(ignore (.Append sb "world")) +(.ToString sb) ;; "hello, world" +(.-Length sb) ;; 12 +(.ToUpper "shout") ;; "SHOUT" +}| + +As the last lines show, you do not have to import every method. @code{(.Method obj args ...)} calls a method, and @code{(.-Property obj)} reads a property, as long as the type of @code{obj} is known at that point. @code{Append} answers the builder again, and a value that is thrown away has to be thrown away on purpose, with @code{ignore}. + +@subsubsection{When .NET fails} + +.NET methods report failure by throwing. There are three ways to turn that into a value: + +@read-list{ +- @code{try}, around any code, which was covered in the first part. +- @code{#:exceptions} on a constructor in @code{import/class}, which makes the constructor answer a @code{Result}. +- @code{(out T)} parameters: a @code{TryParse}-style method, which answers a @code{bool} and puts its result in an @code{out} parameter, becomes a function that answers an @code{Option}. +} + +@codeblock[#:lang "bjolang"]|{ +(import/class + (Uri (: System.Uri (-> string Uri) + #:exceptions (System.UriFormatException)))) + +(import/extern + (parse-int (: System.Int32.TryParse (-> string (out int) (Option int))))) + +(match (Uri. "https://example.com/cards") + ((Ok u) (.-Host u)) ;; "example.com" + ((Err e) (.-Message e))) + +(parse-int "42") ;; (Some 42) +(parse-int "forty-two") ;; None +}| + +@subsubsection{Async methods, and ones that block} + +A .NET method that answers a @code{Task} is imported with @code{#:async}. Calling it from a bjoroutine waits for the task without blocking a thread, and you never write @code{await}: + +@codeblock[#:lang "bjolang"]|{ +(import/extern + (read-text-async (: System.IO.File.ReadAllTextAsync (-> string string) #:async))) + +(defbjo (main) + (println (string-length (read-text-async "deck.bjo"))) + 0) +}| + +A method that blocks its thread should be marked @code{#:blocking}. You then get a warning when you call it from a bjoroutine, and the fix is to call it through @code{(sync (blocking (fun () ...)))}, as in the concurrency section. + +@subsubsection{Generics} + +A generic .NET type is imported with type variables, and a generic method needs a signature that says what they are: + +@codeblock[#:lang "bjolang"]|{ +(import/class + ((Dict %k %v) (: System.Collections.Generic.Dictionary))) + +(import/extern + (dict-try-get (: System.Collections.Generic.Dictionary.TryGetValue + (-> (Dict %k %v) %k (out %v) (Option %v))))) +}| + +Between the two worlds, a @code{Func} is a @code{(-> %a %b)}, an @code{IEnumerable} is a @code{(Seq %a)}, a C# tuple is a @code{Tuple} and @code{T[]} is an @code{(Array %a)}. Constructing a generic .NET class is not supported yet, and neither is matching on .NET objects with constructor patterns. + +@subsubsection{Culture} + +A bjolang program runs in the invariant culture, so a double is always written @code{2.5}, even on a Swedish machine where .NET would write @code{2,5}. If you want the user's own formatting, set @code{CultureInfo.CurrentCulture} yourself. + +@subsubsection{Calling bjolang from C#} + +A module compiles to a static class called @code{_Module}, with a static method for each @code{defun} and a static field for each @code{def}, so C# can call it. Names that are not valid C# are mangled (@code{full-deck} becomes @code{fullsubdeck}), and the types are bjolang's own collections. @code{bjo build -d} writes the generated C# to @code{out.cs} if you want to see what you would be calling. + +@subsection{Macros} + +A macro is a function that the compiler runs while it reads your program. It gets the form you wrote, as data, and answers the form to use instead. Much of what looks like syntax in bjolang, @code{cond}, @code{when}, @code{type/derive} and @code{with-test} among others, is macros from the prelude and the standard library. + +@subsubsection{def/macro} + +A macro is defined with @code{def/macro}, takes three arguments, @code{form}, @code{inject} and @code{compare}, and is almost always written with @code{syntax-match} from @code{(std syntax-match)}: + +@codeblock[#:lang "bjolang"]|{ +(import (std syntax-match)) + +;; (with-each-card (c deck) body ...) runs the body once for every card. +(def/macro (with-each-card form inject compare) + (syntax-match form + ((_ (name deck) body ...) + #'(vec-for-each (fun (,name) ,@body) ,deck)) + (bad (syntax-error bad "(with-each-card (name deck) body ...)")))) +}| + +@code{syntax-match} takes the form apart by shape. @code{_} matches anything (here the macro's own name), a name binds what it matches, and @code{...} after a pattern matches the rest. @code{#'(...)} builds the answer: a template where @code{,x} puts in a piece of syntax, and @code|{,@xs}| splices in a list of them. @code{syntax-error} rejects the input with a message pointing at where the macro was used. + +A pattern like @code{((name value) ...)} matches any number of pairs and binds @code{name} and @code{value} to lists, one element per pair. A quoted name, @code{'else}, matches that identifier and nothing else. + +A macro cannot be used in the module that defines it. It is compiled code that the compiler runs, so it has to be compiled before the code that uses it is read: + +@codeblock[#:lang "text"]|{ +'twice' is a macro defined in this module, and a macro cannot be used where it is defined, +at bad2.bjo:5. Its transformer runs inside the compiler, so it has to be compiled before +whatever uses it is read — which cannot be true of the file it is written in. Move it to a +module of its own and import that. An (include ...) will not do: an included file becomes +part of this one. +}| + +So the macros of the card game go in @code{cardmacros.bjo}, and @code{(import "cardmacros.bjo")} is enough to use them. Macros need no export: every macro in a module arrives with it. + +The input to a macro is a value of the type @code{Syntax}, a union with cases like @code{(SSym s)} for an identifier, @code{(SInt text)} for a number and @code{(SList items)} for a parenthesized form. A macro is an ordinary bjolang function, so it can call other functions, match on the @code{Syntax} directly, and recurse. + +@subsubsection{Hygiene and inject} + +Names that a template introduces are renamed so that they cannot clash with the names at the place the macro is used: + +@codeblock[#:lang "bjolang"]|{ +;; (or-else a b) is a, unless a is 0. +(def/macro (or-else form inject compare) + (syntax-match form + ((_ a b) #'(let ((tmp ,a)) (if (= tmp 0) ,b tmp))))) + +;; in another module +(def tmp 9) +(or-else 0 tmp) ;; 9, not 0 +}| + +The @code{tmp} in the template and the @code{tmp} the caller passed are two different variables, so the answer is the caller's 9. Names the template uses without binding them, like @code{let}, @code{if} and @code{=} here, still mean what they mean in the prelude, even if the calling module has something else by that name. + +Sometimes you do want the macro to bind a name the caller can see. @code{(inject 'name)} makes an identifier that is not renamed: + +@codeblock[#:lang "bjolang"]|{ +;; (aif test then else): in `then`, `it` is what was inside the Some. +(def/macro (aif form inject compare) + (syntax-match form + ((_ test then else) + #'(match ,test + ((Some ,(inject 'it)) ,then) + (None ,else))))) + +(aif (map-try-ref #map(("a" 1)) "a") + (println #"found ${it}") + (println "nothing")) +}| + +@subsubsection{Several forms: begin} + +A macro answers one form. To define several things, answer a @code{(begin ...)}, whose contents are spliced in where the macro was used: + +@codeblock[#:lang "bjolang"]|{ +;; (def/counter name) defines a function that counts how often it is called. +(def/macro (def/counter form inject compare) + (syntax-match form + ((_ name) + #'(begin + (def/mutable count 0) + (: ,name (-> int)) + (defun (,name) + (set! count (+ count 1)) + count))))) + +;; in another module +(def/counter next-id) +(next-id) ;; 1 +(next-id) ;; 2 +}| + +@code{count} is written in the template, so it is renamed like everything else, and a @code{count} in the calling module is not disturbed. @code{next-id} came from the caller, so it is defined under that name. + +@subsubsection{Pattern macros: def/pattern} + +@code{def/pattern} defines a macro that is used in patterns instead of expressions. A pattern macro without arguments can be written as a bare capitalized name: + +@codeblock[#:lang "bjolang"]|{ +;; Face matches a jack, a queen or a king. +(def/pattern (Face form inject compare) + (syntax-match form + (_ #'(or Jack Queen King)))) + +;; in another module +(: court? (-> Card bool)) +(defun (court? c) + (match (record-ref c rank) + (Face #t) + (_ #f))) +}| + +Pattern macros are expanded before the exhaustiveness check, so the compiler sees the @code{or} and checks it like one you wrote yourself. + +@subsubsection{Hash macros: def/hash-extend} + +@code{#map(...)} is a hash macro, and you can write your own with @code{def/hash-extend}. Here is a card literal. The rank and suit are turned into constructors by two helper functions: + +@codeblock[#:lang "bjolang"]|{ +(import (std syntax-match) "deck.bjo") + +(re-export Card Rank Suit) + +;; A number is a Pip, a letter a court card. +(: rank-syntax (-> Syntax Syntax)) +(defun (rank-syntax r) + (syntax-match r + ('J #'Jack) ('Q #'Queen) ('K #'King) ('A #'Ace) + (n (match n + ((SInt _) #'(Pip ,n)) + (_ (syntax-error n "a rank is 2 to 10, J, Q, K or A")))))) + +(: suit-syntax (-> Syntax Syntax)) +(defun (suit-syntax s) + (syntax-match s + ('clubs #'Clubs) ('diamonds #'Diamonds) ('hearts #'Hearts) ('spades #'Spades) + (bad (syntax-error bad "a suit is clubs, diamonds, hearts or spades")))) + +;; #card(Q hearts) is (Card (rank Queen) (suit Hearts)). +(def/hash-extend (card form inject compare) + (syntax-match form + ((_ r s) #'(Card (rank ,(rank-syntax r)) (suit ,(suit-syntax s)))) + (bad (syntax-error bad "#card takes a rank and a suit: #card(Q hearts)")))) +}| + +@codeblock[#:lang "bjolang"]|{ +(import "deck.bjo" "cardmacros.bjo") + +(defun (main) + (println #card(Q hearts)) ;; Q♥ + (println #card[10 spades]) ;; 10♠, either bracket works + (println (court? #card(K clubs))) ;; True + (with-each-card (c [#card(A spades) #card(2 hearts)]) + (println #"a card: ${c}")) + 0) +}| + +A mistake is reported where the literal is written: + +@codeblock[#:lang "text"]|{ +The hash macro 'card' failed at bad.bjo:3: a suit is clubs, diamonds, hearts or spades — in hurts +}| + +The @code{re-export} in @code{cardmacros.bjo} is there because the template writes @code{Card}, @code{Queen} and the rest, and those have to mean something in the module where the macro is used. With the re-export, importing @code{cardmacros.bjo} is enough to make them resolve. It is still a good idea to import @code{deck.bjo} as well, since that is where the @code{->str} implementations live. + +@subsubsection{Literals that elaborate into your unions} + +Many small languages do not need a macro at all. Where a union is expected, a quoted list is turned into the union's cases, and a case can declare with @code{#:tag} the name it is written under: + +@codeblock[#:lang "bjolang"]|{ +(type (: Action (Union (: Play int #:tag play) + (: Say string #:tag say) + (: Pass #:tag pass)))) + +(: script (List Action)) +(def script '((play 3) (say "your turn") pass (play 1))) +;; = (list (Play 3) (Say "your turn") Pass (Play 1)) + +(def n 7) +(def (: more (List Action)) '((play ,n) pass)) +}| + +This is checked by the type checker, not by a macro, so a wrong tag or the wrong number of arguments is a type error: + +@codeblock[#:lang "text"]|{ +Type Error at elab2.bjo:5: `sya` is not a tag of the union elab2/Action and no case of it +carries a list, so this literal cannot be one. Its tags are play, say or pass. +}| + +It only works where the type checker knows a union is expected, which is why @code{script} and @code{more} have types. @code{(std run)}'s commands and @code{(std fmt)}'s layouts are both written this way. + +@subsubsection{What a macro cannot do} + +@read-list{ +- Be used in the module that defines it. +- Be used anywhere but where an expression or a definition goes (or a pattern, for @code{def/pattern}). Not as a @code{loop} clause, a @code{let} binding or a type. +- Replace a special form. A macro called @code{if} is never used. +- See types. Macros are expanded before type checking. +- Be passed around as a value. +- Add an import. Write the import in the file that uses the macro. +} + +@subsection{Testing and the REPL} + +@subsubsection{Tests with (std simpletest)} + +@code{(std simpletest)} is a small test library. @code{(expect label actual wanted)} compares two values, @code{(expect-true label ok)} checks a boolean, and @code{(fail label)} marks a branch that should not be reached. Each prints @code{ok: label} or a line starting with @code{FAILURE:}: + +@codeblock[#:lang "bjolang"]|{ +;; tests/deck-test.bjo +(import (std simpletest) (cardlib deck)) + +(defun (main) + (expect "a deck has 52 cards" (vec-length (full-deck)) 52) + (expect "an ace is worth 11" (card-points (Card (rank Ace) (suit Spades))) 11) + (expect-true "shuffling keeps the cards" (= (vec-length (shuffle (full-deck))) 52)) + 0) +}| + +@codeblock[#:lang "text"]|{ +$ bjo run tests/deck-test.bjo +ok: a deck has 52 cards +ok: an ace is worth 11 +ok: shuffling keeps the cards +}| + +A failure shows both values: @code{FAILURE: deliberately wrong gave 2 but wanted 3}. + +@code{(with-test "name" body ...)} runs a test body in a scope with a deadline. The test gets a timeout that names it, fibers it spawned are waited for, and it fails if it left a file open. Since it is a scope, it has to be in a bjoroutine. + +@code{(with-fake-fs ((path contents) ...) body ...)} replaces the file system with a map for everything inside it. Here is a test of the high-score code from the I/O section, with the score functions moved to a module of their own. The real @code{scores.txt} is not touched: + +@codeblock[#:lang "bjolang"]|{ +(import (std simpletest) (std effect) "scores.bjo") + +(defbjo (main) + (with-test "no file means no scores" + (with-fake-fs () + (expect "empty" (read-scores) []))) + + (with-test "a bad line is skipped" + (with-fake-fs (("scores.txt" "ada 31\nnonsense\nbo 27\n")) + (expect "two scores" (read-scores) [(Tuple "ada" 31) (Tuple "bo" 27)]))) + + (with-test "a saved score reads back" + (with-fake-fs (("scores.txt" "ada 31\n")) + (save-score "cy" 12) + (expect "appended" (vec-length (read-scores)) 2))) + 0) +}| + +@code{with-fake-fs} is written with @code{with-handler}, so the test has to import @code{(std effect)} as well. Tests go in @code{tests/} in a project, and are run with @code{bjo run}. + +@subsubsection{The REPL} + +@code{bjo repl} starts the REPL (inside a project, the project's modules can be imported). It has no line editing of its own, so if @code{rlwrap} is installed, @code{bjo} uses it. An entry is either one expression, whose value is printed, or a group of definitions, which prints the names it defined: + +@codeblock[#:lang "text"]|{ +bjo> (+ 1 2) +3 +bjo> (import "deck.bjo") +bjo> (vec-slice (full-deck) 0 3) +[2♣ 3♣ 4♣] +bjo> (defun (double (: x int)) : int (* x 2)) +double +bjo> (double 21) +42 +}| + +The REPL is not an interpreter. Every entry is compiled by the same compiler as a file, into a small assembly of its own, so it behaves exactly like the same code in a file would. + +A top-level @code{defun} needs a signature at the prompt too. Either write the types on the definition, as with @code{double} above, or type the signature on its own line first, and the next entry picks it up: + +@codeblock[#:lang "text"]|{ +bjo> (: triple (-> int int)) +bjo> (defun (triple x) (* x 3)) +triple +}| + +Redefining a name shadows the old one, but code that was already compiled against the old one keeps calling it: + +@codeblock[#:lang "text"]|{ +bjo> (defun (f (: x int)) : int (+ x 1)) +f +bjo> (defun (g (: x int)) : int (f (f x))) +g +bjo> (defun (f (: x int)) : int (* x 100)) + note: f shadows the one from entry 1. Anything already compiled against that one still calls it. +f +bjo> (g 10) +12 +}| + +The same goes for @code{impl}s. An implementation typed at the prompt is used by the entries after it, not by the ones before. @code{:help} lists the commands, and @code{:quit} or Ctrl-D leaves. diff --git a/_03.Standard-library.sz b/_03.Standard-library.sz new file mode 100644 index 0000000..3bba85f --- /dev/null +++ b/_03.Standard-library.sz @@ -0,0 +1,45 @@ +@section{Part III — The standard library} + +A short tour of the standard library. The full documentation for these modules can be found in the reference documentation under @code{Docs/std/} and @code{prelude.org}. + +@subsection{std/fmt} + +Formatting in Bjolang is divided into two layers: interpolation and layout. +Interpolation provides a simple way to combine mixed scalar types into strings or print them directly using @code{println*} and @code{str*}. +The layout layer provides a composable block algebra for more complex text formatting, such as padding, aligning, wrapping paragraphs, and laying out tables. +For more details, see @code{Docs/std/fmt.org}. + +@subsection{std/rx} + +Bjolang provides a built-in regular expression engine. Unlike languages that use strings for regex (like @code{"[a-z]+"}), Bjolang uses structural S-expressions. This means you never have to double-escape characters, and the compiler validates your patterns at compile time. Built-in character sets and anchors are written as keywords (e.g., @code{:bos}, @code{:digit}), and operations that group or repeat patterns are function-like lists (e.g., @code{(seq ...)}). +For more details, see @code{Docs/std/rx.org}. + +@subsection{std/http} + +The @code{std/http} module allows you to build and send HTTP requests. Sending a request three ways — blocking, suspending, or as an event — is the only thing that differs between the entry points. There is no C# in this module: @code{System.Net.Http} is reached with @code{import/extern}, and the response body is an ordinary @code{TextInputPort}. +For more details, see @code{Docs/std/http.org}. + +@subsection{std/run} + +The @code{std/run} module provides a way to run external programs. A form is one thing to run, written as a quoted literal, like @code{'(cat "hej.txt")}. You can connect programs in a pipe with @code{'(pipe (cat "a") (wc -l))}, and redirect input and output to files. Redirections can nest, and you can even splice computed arguments or write stages in Bjolang. +For more details, see @code{Docs/std/run.org}. + +@subsection{std/random} + +The random module provides a ChaCha20 generator, seeded with 256 bits from the OS CSPRNG, with one generator per thread. The default path locks nothing and shares nothing, making it safe and fast on a bjoroutine. The ambient source has no state to race on, so drawing from multiple threads is efficient. You can generate random integers and more. +For more details, see @code{Docs/std/random.org}. + +@subsection{std/datetime} + +This module provides types and functions for handling time. The clock is an argument (with a default that a program can rebind), and the types refuse operations that mean nothing, such as adding a calendar day to an instant. The module separates @code{Date} (a calendar date without a timezone), @code{Time} (a time of day without a timezone), and @code{DateTime} (a date and time without a timezone), as well as handling timezones. +For more details, see @code{Docs/std/datetime.org}. + +@subsection{text/bjodat} + +bjodat is Bjolang's shapes read as data: no evaluation, no macro expansion, no environment. It is what a package manifest, a lockfile, or a config file is written in. You can parse text straight into a record using @code{bjodat-parse-}, which avoids intermediate trees and allocations. +For more details, see @code{Docs/std/bjodat.org}. + +@subsection{text/json} + +The @code{text/json} module provides JSON parsing and generation capabilities. Like @code{bjodat}, it allows for working with structured data but using the standard JSON format, which is essential for web communication. +For more details, see @code{prelude.org} and related standard library documentation. diff --git a/index.sz b/index.sz new file mode 100644 index 0000000..55a96ce --- /dev/null +++ b/index.sz @@ -0,0 +1,8 @@ +(meta (title "Bjolang manual") + (date 2029-09-29)) + +@toc[#:depth 3] + +@include["_01.Language-basics.sz"] +@include["_02_.Writing-programs.sz"] +@include["_03.Standard-library.sz"]