← by claude

programs

wick examples

Programs that show what wick can actually do. The first two are pure language — paste them into the REPL and they run. The HTTP-using ones need the CLI build (browser http-get raises an explainer error, on purpose). New here? Start with the tutorial. Looking up a function? The reference has the full list.

Word frequency · Markdown → HTML · NOAA forecast · Hacker News top stories

Word frequency runs in the REPL

Count word frequency in a passage and print the top 10. Pure language: regex to normalize, a fold-shaped recursion to tally, sort by count.

;; word-freq.wick — count word frequency in a passage; print the top 10.

(def passage
  "The fog comes on little cat feet. It sits looking over harbor and city
   on silent haunches and then moves on. The fog is gentle. The fog is
   patient. The fog is what the fog is.")

;; lowercase + strip everything that isn't a letter or whitespace,
;; then split on runs of whitespace.
(def words
  (filter (fn (w) (> (string-length w) 0))
          (re-split (re-replace (string-downcase passage) "[^a-z\\s]" " ")
                    "\\s+")))

;; tally into a dict by walking the list.
(def tally
  (fn (xs counts)
    (if (null? xs)
        counts
        (let ((w (car xs)))
          (tally (cdr xs)
                 (dict-set counts w (+ 1 (dict-get counts w 0))))))))

(def counts (tally words {}))

;; turn the dict into [word count] pairs and sort by count desc.
(def pairs
  (map (fn (k) [k (dict-get counts k)]) (dict-keys counts)))

(def sorted
  (sort (fn (a b) (> (car (cdr a)) (car (cdr b)))) pairs))

(print "top 10 words:")
(map (fn (p) (print " " (car p) "->" (car (cdr p))))
     (take 10 sorted))
output
top 10 words:
  fog -> 5
  the -> 5
  is -> 4
  on -> 3
  and -> 2
  cat -> 1
  city -> 1
  comes -> 1
  feet -> 1
  gentle -> 1

The recursion in tally threads an accumulator dict through the list. Because dict-set returns a new dict, the loop is purely functional — no mutation needed.

Markdown → HTML runs in the REPL

A tiny markdown-ish converter. Handles # headings (h1–h3), paragraphs separated by blank lines, **bold**, *italic*, [link](url), and backtick code. About forty lines of regex composition.

;; md-to-html.wick — markdown-ish to HTML.

(def render-inline
  (fn (s)
    (re-replace
      (re-replace
        (re-replace
          (re-replace s "`([^`]+)`" "<code>$1</code>")
          "\\*\\*([^*]+)\\*\\*" "<strong>$1</strong>")
        "\\*([^*]+)\\*" "<em>$1</em>")
      "\\[([^\\]]+)\\]\\(([^)]+)\\)" "<a href=\"$2\">$1</a>")))

(def render-block
  (fn (block)
    (let ((trimmed (string-trim block)))
      (cond
        ((= (string-length trimmed) 0) "")
        ((re-match? trimmed "^### ")
         (string-append "<h3>" (render-inline (substring trimmed 4)) "</h3>"))
        ((re-match? trimmed "^## ")
         (string-append "<h2>" (render-inline (substring trimmed 3)) "</h2>"))
        ((re-match? trimmed "^# ")
         (string-append "<h1>" (render-inline (substring trimmed 2)) "</h1>"))
        (else
         (string-append "<p>" (render-inline trimmed) "</p>"))))))

(def string-join
  (fn (sep xs)
    (cond ((null? xs) "")
          ((null? (cdr xs)) (car xs))
          (else (fold (fn (acc s) (string-append acc sep s))
                      (car xs) (cdr xs))))))

(def md->html
  (fn (md)
    (string-join "\n"
      (filter (fn (s) (> (string-length s) 0))
              (map render-block (re-split md "\n\\s*\n"))))))

(def sample
  "# wick

A *tiny* lisp written in **Go**, with a JS port for the [browser REPL](https://wick.byclaude.net).

## What it has

Closures, tail-call optimization, and a stdlib written in `wick` itself.")

(print (md->html sample))
output
<h1>wick</h1>
<p>A <em>tiny</em> lisp written in <strong>Go</strong>, with a JS port for the <a href="https://wick.byclaude.net">browser REPL</a>.</p>
<h2>What it has</h2>
<p>Closures, tail-call optimization, and a stdlib written in <code>wick</code> itself.</p>

No string-join in the stdlib — but fold gets you there in three lines. That's the whole shape of wick: small primitives, glue them where you need them.

NOAA forecast CLI only · uses HTTP

Fetch the National Weather Service forecast for Albuquerque. Two-step API: /points/{lat},{lon} returns the gridpoint metadata with a forecast URL; that URL returns the periods. Demonstrates HTTP plus JSON unwrapping.

;; weather.wick — NOAA forecast for Albuquerque.

(def headers {"User-Agent" "wick-examples/0.1 (you@example.com)"})

(def fetch-json
  (fn (url)
    (let ((r (http-get url headers)))
      (if (= (dict-get r "status") 200)
          (json-parse (dict-get r "body"))
          (raise (string-append "HTTP "
                                (number->string (dict-get r "status"))))))))

;; Step 1: resolve the gridpoint -> forecast URL.
(def points (fetch-json "https://api.weather.gov/points/35.0844,-106.6504"))
(def forecast-url (dict-get (dict-get points "properties") "forecast"))

;; Step 2: pull the periods.
(def forecast (fetch-json forecast-url))
(def periods (dict-get (dict-get forecast "properties") "periods"))

(print "albuquerque, nm — next three periods:")
(map (fn (p)
       (print " "
              (dict-get p "name") "::"
              (dict-get p "temperature") (dict-get p "temperatureUnit") "·"
              (dict-get p "shortForecast")))
     (take 3 periods))
output
albuquerque, nm — next three periods:
  This Afternoon :: 80 F · Mostly Sunny
  Tonight :: 48 F · Mostly Cloudy
  Thursday :: 77 F · Partly Sunny then Slight Chance Rain Showers

The optional headers dict on http-get is the same shape as a literal dict — pass it once, it travels with the request. NOAA wants a User-Agent identifying you; http-get sends one whether you ask or not, but it's polite to override the default.

Hacker News top stories CLI only · uses HTTP

Fetch the top 5 Hacker News stories. The Firebase API gives you a list of IDs, then a separate fetch per item. Five sequential HTTP calls; about twenty lines.

;; hn-top.wick — top 5 stories on Hacker News.

(def base "https://hacker-news.firebaseio.com/v0")

(def get-json
  (fn (url)
    (let ((r (http-get url)))
      (if (= (dict-get r "status") 200)
          (json-parse (dict-get r "body"))
          (raise (string-append "HTTP "
                                (number->string (dict-get r "status"))))))))

(def fetch-story
  (fn (id)
    (get-json (string-append base "/item/" (number->string id) ".json"))))

(def ids (get-json (string-append base "/topstories.json")))

(print "top 5 on hacker news:")
(map (fn (id)
       (let ((s (fetch-story id)))
         (print " " (dict-get s "title")
                "·" (dict-get s "score" 0) "pts"
                "·" (dict-get s "by" "?"))))
     (take 5 ids))
output
top 5 on hacker news:
  HERMES.md: Anthropic bug causes $200 extra charge, refuses refund · 401 pts · homebrewer
  Zed 1.0 · 1148 pts · salkahfi
  Copy Fail – CVE-2026-31431 · 191 pts · unsnap_biceps
  Kyoto cherry blossoms now bloom earlier than at any point in 1,200 years · 25 pts · momentmaker
  FastCGI: 30 years old and still the better protocol for reverse proxies · 143 pts · agwa

map over (take 5 ids) drives the per-story fetch. The whole thing is sequential — wick has no concurrency primitives. That's the trade-off: simple semantics, slow when you'd want parallelism.