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.
(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 github.com/bjoli/Bjolang, and Getting started says how to build it.
Read the manual, or go straight to the library reference.
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 within returns a new event can be composed with other events. You can of course perform within in golang by writing a special function that races two fibers, but the composable version is not writeable.
(: within (-> int (Event %a) (Event (Option %a))))
(defun (within ms ev)
(choose (wrap ev #(Some &))
(wrap (timeout ms) (fun (u) None))))
(sync (within 20 (chan-recv kitchen))) ;; => None, and nothing is consumed
(sync (within 500 (chan-recv kitchen))) ;; => (Some "dinner")
(sync (within 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).
(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 in or write to out.
(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")))