Bjolang is only installed from source. There are no releases or packages: you clone the repository at @link["https://github.com/bjoli/Bjolang"]{github.com/bjoli/Bjolang} and build it where it is.
It needs only two things: dotnet 10 and an f# compiler. The .NET 10 SDK comes with both. Once that is installed, clone the repository and build the runtime, the compiler and the standard library:
If you want to make sure everything works, run @code{./run_tests.py} and @code{./run_bjo_tests.py} in the same directory. Then you can use the bjo executable in the bjo directory: @code{./bjo/bjo run hello.bjo}. Personally I alias a shell command to the path of bjo, since there currently is no way to install it properly. Compiled programs find the standard library in the directory you built it in, so leave the checkout where it is.
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#. An int it returns is the program's exit code, 0 when it ran correctly; a main that returns anything else exits with 0.
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 function, and thus has to declare its 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 void. void is the name for nothing: the unit type, whose one value is what println gives back.
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:
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"))
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.
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}:
;; 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.
@subsubsection{Local functions and inline signatures}
A local function defined inside another function looks like a top-level @code{defun}, but does not need a separate @code{(: ...)} type signature because local types are inferred automatically with Hindley-Milner type inference. In fact, local functions do not support separate @code{(: ...)} signatures at all.
When you want to document types explicitly or constrain inference, you can write the signature inline directly on the @code{defun} by annotating each parameter with @code{(: param Type)} and following the parameter list with @code{: ReturnType}:
@codeblock[#:lang "bjolang"]{
(defun (process-numbers ls)
(defun (clamp (: n int) (: max int)) : int
(if (> n max) max n))
(list-map #(clamp & 100) ls))
}
Parameters can use any type, including type variables for generic local functions:
Note that if you supply an inline signature, all parameters and the return type must be annotated together; partial inline annotations are not permitted. Trait constraints (@code{where} clauses) and keyword argument defaults cannot be written inline and require top-level signatures.
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.
@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.
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:
;; 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.
A newtype is the opposite of an alias: a type of its own, which the compiler will not mix up with the type it wraps, but which is that type when the program runs, so it costs nothing.
@codeblock[#:lang "bjolang"]|{
(type (: UserId (Newtype int)))
(type (: OrderId (Newtype int)))
(def u (UserId 7)) ;; built by its name
(def (UserId n) u) ;; taken apart by a pattern: n is 7
(: (-> UserId string))
(defun (lookup-user id) ...)
(lookup-user (OrderId 7)) ;; a compile error: an OrderId is not a UserId
}|
A newtype does not get the traits of the type it wraps: @code{(+ u u)} is an error, though @code{(+ 7 7)} is not. It gets those you give it, as the section on traits shows.
Records and structs are direct mappings to c# records and immutable structs. The constructor is the type name, together with parenthesised fields. As follows.
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!}.
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 value of your own type prints it as you would build it, for example @code{(Card (rank Queen) (suit Hearts))} or @code{(Pip 7)}. The compiler writes this for each type you declare. To print a type another way, implement @code{Displayed} (see the traits section).
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
@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)))
A field can also be read with @code{(.field value)}, which is the same as @code{record-ref}. On its own, @code{.field} is a function, so it can be passed around:
@codeblock[#:lang "bjolang"]|{
(.tricks ada) ;; 0
(list-map .tricks (list ada (won-trick ada))) ;; '(0 1)
}|
@subsubsection{Reading and changing nested data}
Data is often a record holding a map holding a vec of records. Reading something deep inside it, or making a copy with one thing changed deep inside it, is a lot of @code{record-ref} and @code{record-set} by hand. The prelude has four macros for it instead. Each takes a value and a @em{path}: a list of steps, where a step written @code{.field} is a field, and any other step is a key into a map or an index into a vec.
A key is read with @code{ref} and written with @code{ref-set}, so the path can go through anything that implements the traits behind them: @code{Refable} to read, and @code{Settable} to write, which @code{Vec}, @code{Map} and @code{OrderedMap} do. A field step can read a .NET property too, as in @code{(get team .players 0 .name .-Length)}, but only a Bjolang record or struct can be copied with a field changed, so @code{set} and @code{update} refuse one.
@item{@code{set} does not read the last key, so setting a key a map does not have adds it. A key that is missing earlier in the path throws, as @code{ref} does.}
@item{@code{set} and @code{update} evaluate the value, then each key from left to right, then the new value or the function, each of them exactly once.}
@item{These are macros, so a step is a field or a key by how it is written, not by its type: @code{.name} is always a field, and anything else is always a key.}
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.
| @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
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:
| @code{(:between lo hi)} | from @code{lo} to @code{hi} |
| @code{(:greater-than lo)} | more than @code{lo} |
| @code{(:less-than hi)} | less than @code{hi} |
}
@code{:between} includes both of its ends: it is @code{:at-least} and @code{:at-most} together, as @code{version-between} is in a manifest. Use @code{:greater-than} and @code{:less-than} when an end must not be included, which a @code{double} needs, because it has no next value below or above a bound.
A comparison is a pattern like any other, so you can put it inside other patterns: in a constructor, in a field of a record, in a list, vec or array pattern, and in @code{or} and @code{and}:
@codeblock[#:lang "bjolang"]|{
(: grade (-> int string))
(defun (grade points)
(match points
((:less-than 0) "invalid")
((:between 0 59) "fail")
((:at-least 60) "pass")))
(: letter (-> Char string))
(defun (letter c)
(match c
((:between #\a #\z) "lower case")
((:between #\A #\Z) "upper case")
(_ "something else")))
(match card
((Card (rank (Pip (:at-least 7)))) "a high pip card")
(_ "something else"))
(match reading
((Some (and n (:greater-than 100))) #"too high: ${n}")
The bounds must be number or character literals, because the comparison is compiled to a constant pattern. To compare with a value that is computed at run time, use a @code{#:when} guard.
The checker adds up the ranges of an integer or a character, so @code{grade} above needs no catch-all: its three ranges include every @code{int}. A clause whose range is already matched by the clauses above it is an error, as any clause that can never run is. The checker does not add up the ranges of a @code{double} or a @code{decimal}. A @code{double} can be NaN, which no comparison matches, so a match on one must end in a catch-all.
@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:
((: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"
Comparisons of integers and characters are different: the checker knows which values they match. The counterexample is then a value in a gap between the ranges:
@codeblock[#:lang "bjolang"]|{
(match n
((:less-than 0) "negative")
((:at-least 1) "positive"))
}|
@codeblock[#:lang "text"]|{
Pattern Error at example.bjo:2: this match does not cover every value. 0 reaches no clause.
@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:
;; 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:
;; (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:
| @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.
(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}:
(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}:
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:
;; (Err <the exception>), 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:
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.
| @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:
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 point, not by the rules of any language, so the order is the same on every machine.
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:
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<T>}, 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):
(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:
| @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:
@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:
@code{(:abandon test)} in the inner loop ends it early, and the outer loop goes on with its next round. In the outer loop there is nothing to go on with, so there it is an error:
A nested loop like this runs to the end of the round: the clauses after the inner @code{:for} belong to the inner loop. When the round should go on after the inner loop, with what it computed, write the inner loop as a @code{:subloop} form instead. It is a loop of its own inside the round, and its accumulators are there for the clauses after it, with their final values:
@codeblock[#:lang "bjolang"]|{
(loop (:for row (list (list 1 2) (list 3 4 5)))
(:subloop (:for x row)
(:acc sum (summing x))
(:acc n (counting x)))
(:acc out (listing (sum . n)))
=> out) ;; '((3 . 2) (12 . 3))
}|
With @code{=> (name expr)} at its end, only @code{name} is bound after the form, to @code{expr}:
@codeblock[#:lang "bjolang"]|{
(loop (:for row rows)
(:subloop (:for x row)
(:acc sum (summing x))
(:acc n (counting x))
=> (avg (if (= n 0) 0 (/ sum n))))
(:acc out (listing avg))
=> out)
}|
The form's accumulators start over every time it runs, and a form that runs zero times leaves them at their start values. Its own variables are never visible after it. Inside it, two clauses are about the form:
@read-list{
- @code{(:finish-subloop test)} ends the form here, and the round goes on after it with the accumulators as they are.
- @code{(:abandon-subloop test)} throws the form and the rest of the round away, and the outer loop goes on with its next round.
}
@code{:when}, @code{:abandon}, @code{:while} and @code{:until} inside the form are about the form's own rounds. @code{:finish} still ends the whole loop.
A variable inside a loop cannot reuse the name of an accumulator, of a @code{:with}, or of a variable of an outer loop: @code{(:for x xs) (:subloop) (:for x x)} is an error. Pick another name.
@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:
@code{:finish} leaves the loop before the rest of the round runs, and @code{:final} leaves it after. @code{:when-let} and @code{:finish-let} bind a pattern that may fail, and skip the round or leave the loop when it does. The names follow one rule: @em{finish} stops and goes on @em{after} the loop, @em{abandon} stops and goes @em{up} to the next round of the outer loop.
@code{(:finish test value)} leaves the loop with @code{value} as its result, and the @code{=>} expression is not run. The value has to have the same type as the @code{=>} expression:
@codeblock[#:lang "bjolang"]|{
;; The sum, or -1 as soon as a negative number shows up.
(loop (:for x (list 1 2 -3 4))
(:finish (< x 0) -1)
(:acc total (summing x))
=> total)
;; -1
}|
@code{:break} and @code{:break-let} are the old names of @code{:finish} and @code{:finish-let}. They still work, with a warning.
@subsubsection{Looping while something holds: :while and :until}
@code{(:while test)} keeps the loop going while @code{test} holds, and @code{(:until test)} until it does. Neither binds anything, so a loop that has no collection to walk can start with one:
@codeblock[#:lang "bjolang"]|{
(loop (:until (port-eof? port))
(:let line (read-line port))
(:do (println line)))
}|
The test is checked before every round, the first one too, so the loop may run zero times. Next to a @code{:for}, the loop ends at whichever stops first, and the test can use the @code{:for}'s variable:
@codeblock[#:lang "bjolang"]|{
(loop (:for x (list 1 2 3 4 5))
(:until (> x 3))
(:acc out (listing x))
=> out)
;; '(1 2 3)
}|
Since the test runs before the round, it cannot see what the round computes. To stop on that, put a @code{:finish} after it: @code{(:let line (read-line/opt port)) (:finish (none? line))}.
A loop has to start with a @code{:for}, a @code{:with}, a @code{:while} or an @code{:until}, since the other clauses belong to the round that it opens.
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.
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:
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:
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
A @code{string} is stored as UTF-8, and is always valid UTF-8. A @code{char} is not a byte, though: it is a whole Unicode code point. An emoji is one @code{char}, even though it takes four bytes in the string. That leads to two different lengths:
For the same reason there is no @code{string-ref} that takes an index. Finding the n:th character of a UTF-8 string means walking it, so walking is what you do: with a @code{loop}, a fold, or a cursor.
(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?}.
All of these compare strings ordinally, byte by byte, which for UTF-8 is the order of the code points, 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.
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. Positions are always cursors: nothing takes a byte index, and the searches answer cursors too.
@code{substring}, @code{string-trim} and @code{string-split} copy the text they answer. A slice is the same text without the copy: a string that shares the bytes of the string it was taken from. Only the forms ending in @code{/slice} make one:
@codeblock[#:lang "bjolang"]|{
(substring/slice s start end)
(string-trim/slice s)
(string-trim-left/slice s)
(string-trim-right/slice s)
(string-split/slice s sep)
}|
A slice is a @code{string} like any other. It equals, hashes and orders by its text, so it finds a map key with that text, and it can be passed anywhere a string can. That makes slices the thing to reach for when a scanner cuts many short pieces out of one text and only looks at them:
@codeblock[#:lang "bjolang"]|{
;; "name = Björn; age = 42" into a map, without copying a key or a value
;; before it is needed.
(: settings (-> string (Map string string)))
(defun (settings line)
(loop (:for field (string-split/slice line ";"))
(:let parts (string-split/slice field "="))
(:when (= (vec-length parts) 2))
(:acc m (mapping ((string-copy (string-trim/slice (vec-ref parts 0)))
. (string-copy (string-trim/slice (vec-ref parts 1))))))
@item{@strong{Its cursors are its source's.} A cursor found in a slice, by @code{string-search}, @code{string-index} or a regex, is a position in the string the slice was taken from, and @code{(string-cursor-start sl)} is where the slice begins in it.}
@item{@strong{It keeps all of its source alive.} A slice of ten bytes out of a file of a megabyte holds on to the megabyte. That is why the plain forms copy, and why the example copies what it keeps: @code{(string-copy s)} gives a string bytes of its own, and is @code{s} itself when it already has them.}
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.
- @code{written} gives the value as the reader reads it, as Scheme's @code{write} does. A string is in quotes, a char is @code{#\a}, a number keeps the suffix of its type (@code{21L}), and a double always has a point (@code{3.0}). The REPL shows a result this way.
- @code{displayed} gives the value for a person to read, as Scheme's @code{display} does. A string is its text, and a number has no suffix. @code{println}, @code{print} and interpolation use it.
;; prints (Fruit (name "banana") (amount 5) (ripeness Rotten))
}|
Every type you declare is written as you would build it. A type that prints differently for a person implements @code{Displayed}, as the card game did for @code{Card}; one that is written differently implements @code{Written}. The traits section has more. @code{->str} is deprecated: use @code{displayed}.
For the basic types there are also named conversions:
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:
;; 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"]
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) "?"))
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.
A trait is a set of functions that a type can implement. It is how bjolang does what other languages do with interfaces, type classes or overloading: @code{=} is a trait method, so is @code{displayed}, and so are @code{length}, @code{fold} and @code{compare}.
@subsubsection{Declaring and implementing a trait}
@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:
;; 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
(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:
@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}, @code{Written} and @code{Displayed} you may leave the @code{where} out: the compiler adds them by itself when a function uses @code{=}, @code{written} or @code{displayed} on a type variable.
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.
@item{Two implementations may not overlap. With @code{(List int)} already there, @code{(impl (Describe (List %a)) ...)} is refused, since a @code{(List int)} would then have two.}
@item{The type has to be known where the call is. @code{(describe (list))} is an error, because an empty list could be either, and so is a call at @code{(List %a)} in a generic function: neither implementation is written for every element type. Give such a value an annotation, or write one implementation for @code{(List %a)} with a @code{where}, as in the next section.}
@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:
A blanket implementation is one for a bare type variable. It applies to every type that has no implementation of its own, and a more specific implementation always wins over it. A type whose head has implementations only for other arguments, such as a @code{(List bool)} beside the @code{(List int)} above, is an error rather than the blanket. Only the module that declares a trait may write its blanket:
A trait may say that every type implementing it implements another trait too, with a @code{where} in the trait itself. Something that greets must have a name:
@code{introduce} asks only for @code{Greet}, and may still call @code{name-of}: a type that can greet is known to have a name. An implementation of @code{Greet} for a type with no @code{Named} is a compile error at the implementation.
The prelude's @code{Ord} requires @code{Eq} this way, so a function with @code{(where (Ord %a))} can use @code{=} as well as @code{compare}. @code{Displayed} requires @code{Written} in the same way.
@subsubsection{Giving a newtype the traits of the type it wraps}
A newtype is the type it wraps when the program runs, so it can borrow that type's implementation of a trait as it is, with @code{#:via}:
@codeblock[#:lang "bjolang"]|{
(type (: Meters (Newtype double)))
(impl (Eq Meters) #:via double)
(impl (Ord Meters) #:via double)
(impl (Num Meters) #:via double) ;; + - * /
(impl (Ordered Meters) #:via double) ;; < > <= >=
(+ (Meters 1.5) (Meters 2.0)) ;; (Meters 3.5)
(+ (Meters 1.5) 2) ;; a number literal is a Meters too
}|
@code{type/derive} writes these for you, for any trait, when the type is a newtype:
@codeblock[#:lang "bjolang"]|{
(type/derive (Eq Ord Num Ordered)
(: Meters (Newtype double)))
}|
A trait that should behave differently is written by hand instead. That is the other use of a newtype: the same data with a different behaviour, such as an @code{Eq} that ignores case on a @code{(Newtype string)}. Sets and maps of the newtype use that implementation, not the wrapped type's.
@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)))))
The compiler writes a @code{Written} for every type you declare, so a value prints as you would build it: @code{(Card (rank Queen) (suit Hearts))}, @code{(Pip 7)}, @code{Hearts}. A generic type is written when its type arguments are. An @code{#:opaque} type is written as @code{#<Name>}, and a field that holds a function as @code{#<procedure>}. @code{Displayed} has a blanket implementation that displays a value as it is written.
To print a type your own way, implement @code{Displayed} for the text a person reads, as the card game did, or @code{Written} to change the written form too. The method to give is @code{display-into} (or @code{write-into}), which adds the text to a string builder; @code{displayed} and @code{written} then come with the trait:
@codeblock[#:lang "bjolang"]|{
(type (: Money (Union (: Kr int))))
(impl (Displayed Money)
(defun (display-into m b)
(match m ((Kr n) (stringbuilder-add-string! b #"${n} kr")))))
(println (Kr 42)) ;; 42 kr
(written (Kr 42)) ;; "(Kr 42)"
}|
A container that holds other values calls @code{write-into} or @code{display-into} on them with the same builder. @code{elements-print-into} and @code{entries-print-into} from the prelude lay out the elements of a collection, or the entries of a map, for you.
@code{->str} is deprecated. It still works, through @code{displayed}, but each use gives a warning.
@subsubsection{Values of different types in one collection: dyn}
A list holds values of one type. When you need a collection of different types that all implement the same trait, pack each value as a @code{(dyn Trait)}:
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:
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
(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.
A number with a suffix is of that type. A decimal point or an exponent makes it a real number, which is a @code{double}, a @code{float} or a @code{decimal}, and never an integer:
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}, or a @code{double} if it is real, only when nothing says otherwise:
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}, and @code{(doubled 2.5)} that 2.5 has a fraction.
@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}:
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:
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.