bjomepage/index.sz
Linus Björnstam c9486122fe within takes a Duration
🤖 Generated with [ECA](https://eca.dev) (anthropic/claude-opus-5-5)

Co-Authored-By: eca-agent <git@eca.dev>
2026-10-20 18:07:47 +02:00

85 lines
3.8 KiB
Text

(meta (title "Bjolang")
(listing? #f)
(show-title? #f))
@div["logo"]{@image["/_resources/logo.svg" #:alt "Bjolang"]}
Bjolang is a Lisp for .NET. It has types that are inferred, unions that are
matched exhaustively, traits, hygienic macros, and bjoroutines, and it
compiles to C#. It is a small experiment in what I would like in a programming language, and in api design.
I built it with liberal use of ai, so beware.
@codeblock[#:lang "bjolang"]|{
(type (: Shape (Union (: Circle double)
(: Rect double double))))
(: area (-> Shape double))
(defun (area shape)
(match shape
((Circle r) (* 3.14159 (* r r)))
((Rect w h) (* w h))))
(defun (main args)
(loop (:for s [(Circle 1.0) (Rect 2.0 3.0)])
(:do (println #"area: ${(area s)}")))
0)
}|
Bjolang is installed from source only. The code is at
@link["https://github.com/bjoli/Bjolang"]{github.com/bjoli/Bjolang}, and
@link["manual/manual.html#getting-started"]{Getting started} says how to
build it.
@link["manual/"]{Read the manual}, or go straight to the
@link["manual/modules/"]{library reference}.
@section{more examples}
ConcurrentML brings the multithreading story for bjolang into the 1990s. It uses events that can be combined and synced on, meaning you can build trees of events and then preform them. This is indeed an extra layer for most operations in cml compared to CSP-inspired things, but allows us to write composable events. The function @code{within} returns a new event can be composed with other events. You can of course perform @code{within} in golang by writing a special function that races two fibers, but the composable version is not writeable.
@codeblock[#:lang "bjolang"]{
(: within (-> Duration (Event %a) (Event (Option %a))))
(defun (within d ev)
(choose (wrap ev #(Some &))
(wrap (timeout d) (fun (u) None))))
(sync (within (milliseconds 20) (chan-recv kitchen))) ;; => None, and nothing is consumed
(sync (within (milliseconds 500) (chan-recv kitchen))) ;; => (Some "dinner")
(sync (within (milliseconds 20) (spawn-evt (count-to 1000)))) ;; works on fibers too
}
This sums every line and every char read from stdin. When the port is closed, the result is printed using the show function from (std fmt).
@codeblock[#:lang "bjolang"]{
(import (only (std ports) port->seq))
(import (std fmt))
(defun (main args)
(def line-seq (port->seq read-line (deref current-input-port)))
(loop (:for line line-seq)
(:acc lines (summing 1))
;; A :for after a non-:for clause opens a new level,
;; so this loops over every char in the line.
(:for ch line)
(:acc chars (summing 1) :when (not (char-whitespace? ch)))
=> (show [(tabular [["Lines:" nl "Chars:" nl]
[lines nl chars nl]])]))
0)
}
Bjolang does type elaboration on literals. That means that if a function takes a (List CmdStuff) argument and you pass it '(pipe (cat "notes.txt") ,shout (grep "bomb")) it gets elaborated into (list (CmdPipe (Cmd 'cat (CmdString "notes.txt") (CmdFilter shout) (Cmd 'grep "bomb")))). For the type elaboration in (std run) this is not quite typesafe, even though it catches most of the issues, but with some care you can make type safe DSLs that can be typechecked.
The example below pipes the output (as in a commandline pipe) from a file through a bjolang function that upcases it and then greps it for "bomb". The shout function is completely async, and suspends if it cannot read from @code{in} or write to @code{out}.
@codeblock[#:lang "bjolang"]{
(import (std run))
;; Filter is a function type described in (std run)
(: shout Filter)
(defbjo (shout in out)
(loop (:for ch (in-port in read-char/opt))
(:do (write-char out (char-upcase ch)))))
(run/string '(pipe (cat "notes.txt") ,shout (grep "BOMB")))
}