Examples
All examples are in the examples/ directory of the repository. Run any of them with:
BEERPATH=lib ./bin/beerlang examples/<file>.beer
Hello World HTTP Server
A minimal HTTP server that returns JSON.
(ns hello-server
(:require [beer.http :as http]
[beer.json :as json]))
(defn handler [req]
{:status 200
:headers {"content-type" "application/json"}
:body (json/emit {:message "Hello from Beerlang!"
:method (:method req)
:uri (:uri req)})})
(http/run-server handler {:port 8080})
# Test it
curl http://localhost:8080/
JSON REST API
An in-memory CRUD API using beer.http, beer.json, and atoms for safe concurrent state.
(ns json-api
(:require [beer.http :as http]
[beer.json :as json]))
(def *store* (atom {}))
(def *next-id* (atom 1))
(defn next-id! []
(let [id @*next-id*]
(swap! *next-id* inc)
id))
(defn store-put! [item]
(let [id (next-id!)
item-with-id (assoc item :id id)]
(swap! *store* assoc id item-with-id)
item-with-id))
(defn json-response [status body]
{:status status
:headers {"content-type" "application/json"}
:body (json/emit body)})
(defn ok [body] (json-response 200 body))
(defn created [body] (json-response 201 body))
(defn not-found [msg] (json-response 404 {:error msg}))
(defn bad-req [msg] (json-response 400 {:error msg}))
(defn parse-int [s]
(try (int (read-string s))
(catch _ nil)))
(defn handler [req]
(let [method (:method req)
uri (:uri req)]
(cond
(and (= method "GET") (= uri "/items"))
(ok (vec (vals @*store*)))
(and (= method "POST") (= uri "/items"))
(let [body (try (json/parse (:body req)) (catch _ nil))]
(if (map? body)
(created (store-put! body))
(bad-req "body must be a JSON object")))
(and (= method "GET") (str/starts-with? uri "/items/"))
(let [id (parse-int (subs uri 7))]
(if-let [item (and id (get @*store* id))]
(ok item)
(not-found (str "item " (subs uri 7) " not found"))))
(and (= method "DELETE") (str/starts-with? uri "/items/"))
(let [id (parse-int (subs uri 7))]
(if (and id (do (swap! *store* dissoc id) true))
(ok {:deleted id})
(not-found "not found")))
:else
(not-found (str "no route for " method " " uri)))))
(println "JSON API listening on http://localhost:8080")
(http/run-server handler {:port 8080})
curl http://localhost:8080/items
curl -X POST http://localhost:8080/items \
-H 'Content-Type: application/json' \
-d '{"name":"widget","price":9.99}'
curl http://localhost:8080/items/1
curl -X DELETE http://localhost:8080/items/1
Channels
CSP-style communication between cooperative tasks: fan-out and pipeline patterns.
(ns channels-example)
;; 1. Basic send / receive
(let [c (chan)]
(spawn (fn [] (>! c "hello from a task")))
(println "received:" (<! c)))
;; 2. Buffered channel — sender doesn't block until buffer full
(let [c (chan 3)]
(>! c 1)
(>! c 2)
(>! c 3)
(println "buffered:" (<! c) (<! c) (<! c)))
;; 3. Fan-out: distribute 16 jobs across 4 workers
(defn make-worker [id jobs results]
(spawn (fn []
(loop []
(let [job (<! jobs)]
(when job
(>! results {:worker id :job job :result (* job job)})
(recur)))))))
(let [jobs (chan 20)
results (chan 20)]
(doseq [i (range 4)]
(make-worker i jobs results))
(doseq [j (range 16)]
(>! jobs j))
(close! jobs)
(let [total (loop [i 16 acc 0]
(if (zero? i) acc
(recur (dec i) (+ acc (:result (<! results))))))]
(println "sum of squares 0..15 =" total))) ;=> 1240
;; 4. Pipeline: source → double → stringify
(defn pipeline-stage [in xf out]
(spawn (fn []
(loop []
(let [v (<! in)]
(when v (>! out (xf v)) (recur))))
(close! out))))
(let [source (chan 10)
doubled (chan 10)
stringed (chan 10)]
(pipeline-stage source #(* % 2) doubled)
(pipeline-stage doubled #(str "v=" %) stringed)
(doseq [i (range 5)] (>! source i))
(close! source)
(loop []
(let [v (<! stringed)]
(when v (println v) (recur)))))
Atoms
Thread-safe mutable state. swap! is atomic — safe to call from concurrent tasks.
(ns atoms-example)
;; Basic usage
(def x (atom 0))
(swap! x inc)
(swap! x + 10)
(println @x) ;=> 11
;; Atoms hold any value
(def config (atom {:host "localhost" :port 8080 :debug false}))
(swap! config assoc :debug true)
(swap! config update :port inc)
(prn @config) ;=> {:host "localhost" :port 8081 :debug true}
;; Safe concurrent counter: 100 tasks × 100 increments = 10000
(def counter (atom 0))
(let [tasks (map (fn [_]
(spawn (fn []
(loop [i 100]
(when (pos? i)
(swap! counter inc)
(recur (dec i)))))))
(range 100))]
(doseq [t tasks] (await t)))
(println @counter) ;=> 10000
;; compare-and-set! for optimistic lock-free updates
(def flag (atom :idle))
(compare-and-set! flag :idle :running) ;=> true
(compare-and-set! flag :idle :running) ;=> false (already :running)
Actors
beer.hive provides Erlang-style message-passing actors with a registry and supervisors.
(ns actors-example
(:require [beer.hive :as hive]))
;; Actor handlers are pure functions: (state, msg) → new-state
;; Return {:reply val :state s} to respond to ask.
;; Return {:stop final} to stop the actor.
(defn counter-handler [state msg]
(cond
(= msg :inc) (inc state)
(= msg :dec) (dec state)
(= msg :reset) 0
(= msg :get) {:reply state :state state}
(= msg :stop) {:stop state}
:else state))
;; 1. Counter actor
(let [pid (hive/spawn-actor counter-handler 0)]
(hive/send pid :inc)
(hive/send pid :inc)
(hive/send pid :dec)
(println "counter:" (hive/ask pid :get)) ;=> 1
(hive/send pid :reset)
(println "after reset:" (hive/ask pid :get)) ;=> 0
(hive/stop pid))
;; 2. Named actor registry
(hive/spawn-actor
(fn [_ msg] (println "[log]" msg) nil)
nil
{:name :logger})
(let [pid (hive/whereis :logger)]
(hive/send pid "hello from named actor"))
;; 3. Key-value store actor
(defn kv-handler [store msg]
(let [op (get msg :op) key (get msg :key)]
(cond
(= op :put) (assoc store key (get msg :val))
(= op :get) {:reply (get store key) :state store}
(= op :del) (dissoc store key)
(= op :all) {:reply store :state store}
:else store)))
(let [kv (hive/spawn-actor kv-handler {})]
(hive/send kv {:op :put :key :name :val "Beerlang"})
(hive/send kv {:op :put :key :version :val "0.1.0"})
(println "name:" (hive/ask kv {:op :get :key :name}))
(prn (hive/ask kv {:op :all}))
(hive/stop kv))
;; 4. Supervisor
(let [sup (hive/supervisor :one-for-one
[(fn [] (hive/spawn-actor counter-handler 0 {:name :supervised-counter}))])]
(println "supervisor started with" (count (:children sup)) "child(ren)"))
Testing
beer.test provides deftest, is, and testing — familiar to anyone who has used clojure.test.
(ns testing-example
(:require [beer.test :as t]))
(defn fizzbuzz [n]
(cond
(zero? (mod n 15)) "FizzBuzz"
(zero? (mod n 3)) "Fizz"
(zero? (mod n 5)) "Buzz"
:else (str n)))
(defn factorial [n]
(loop [i n acc 1]
(if (<= i 1) acc
(recur (- i 1) (* acc i)))))
(t/deftest fizzbuzz-test
(t/testing "multiples of 3"
(t/is (= "Fizz" (fizzbuzz 3)))
(t/is (= "Fizz" (fizzbuzz 99))))
(t/testing "multiples of 15"
(t/is (= "FizzBuzz" (fizzbuzz 15)))))
(t/deftest factorial-test
(t/is (= 1 (factorial 1)))
(t/is (= 120 (factorial 5)))
(t/is (= 2432902008176640000 (factorial 20))))
(t/deftest exception-test
(t/testing "throw and catch"
(let [threw (try
(throw {:type :divide-by-zero})
false
(catch e (= (:type e) :divide-by-zero)))]
(t/is threw))))
(System/exit (t/run-tests))