Files
typlisp/dual-mode.md
2026-01-30 09:58:55 -10:00

382 lines
8.9 KiB
Markdown

# Dual Mode: Hiccup + Raw Escapes
Both systems coexist. Use whichever fits the moment.
## The Two Modes
```clojure
;; HICCUP - when you need programmatic structure
[:heading {:level 1} "Title"]
;; RAW - when you just want to write
#t"= Title"
```
## Mixing Freely
```clojure
[;; Hiccup for structured parts
[:heading {:level 1} title]
;; Raw escape for prose-heavy sections
#t"
This is just regular Typst content. I can write *bold* and _italic_
without all the brackets. Much nicer for prose.
- Bullet one
- Bullet two
"
;; Back to hiccup for programmatic stuff
[:table {:columns 2}
(for [row data]
(list (:name row) (:value row)))]
;; Raw with interpolation
#t"The answer is ~(calculate-answer data)."
;; Nested: raw inside hiccup (use :block for container)
[:block {:inset "1em" :fill "gray.lighten(90%)"}
#t"This is a _sidebar_ with easy formatting."]
;; Nested: hiccup inside raw
#t"
= Section
Here's a dynamic table:
~(render-table results)
And back to prose.
"]
```
## Escape Syntax
```clojure
;; Short string
#t"= Heading"
;; Multi-line block
#t"
Multiple lines
of typst content
"
;; Content with quotes (must escape)
#t"He said \"hello\" and *left*."
```
## Interpolation Inside Raw
Using `~()` - the unquote concept from Clojure's syntax-quote:
```clojure
;; Simple expression
#t"The value is ~(expr)."
;; Hiccup expression returns content
#t"Click ~([:link {:src url} label]) to continue."
;; Conditional
#t"Status: ~(if done? 'Complete' 'Pending')"
;; Loop
#t"
= Items
~(for [i items]
(str '- ' (:name i) '\n'))
"
;; Or loop returning hiccup (items are direct list children)
#t"
= Items
~[:list (for [i items] (:name i))]
"
```
Same mental model as macro unquote:
```clojure
`(let [x ~expr] ...) ; macro: escape to eval
#t"Value: ~(expr)" ; template: escape to eval
```
## Full Example
```clojure
(ns my-paper.core
(:require [clojure-typst.core :refer :all]))
(def authors
[{:name "Alice Chen" :affiliation "MIT" :email "alice@mit.edu"}
{:name "Bob Smith" :affiliation "Stanford" :email "bob@stanford.edu"}])
(def results
[{:method "Baseline" :accuracy 0.72}
{:method "Ours" :accuracy 0.94}])
(defn author-card [{:keys [name affiliation email]}]
[:block {:inset "1em"}
[:strong name] [:linebreak]
[:emph affiliation] [:linebreak]
[:link {:src (str "mailto:" email)} email]])
(defn results-table [data]
[:table {:columns 2 :align ["left" "right"]}
[:strong "Method"] [:strong "Accuracy"]
(for [{:keys [method accuracy]} data]
(list
method
(format "%.1f%%" (* 100 accuracy))))])
(def paper
[[:heading {:level 1} "A Very Important Paper"]
;; Programmatic author list
[:block {:align "center"}
(interpose [:h "2em"]
(map author-card authors))]
;; Prose in raw mode - much nicer to write
#t"
= Abstract
We present a novel approach to solving important problems.
Our method achieves *state-of-the-art* results on several
benchmarks while maintaining computational efficiency.
= Introduction
The problem of _important things_ has long challenged researchers.
Previous approaches @citation1 @citation2 have made progress but
fundamental limitations remain.
In this work, we propose a new framework that addresses these
limitations directly. Our key contributions are:
+ A novel architecture for processing data
+ Theoretical analysis of convergence properties
+ Extensive empirical evaluation
= Methods
"
;; Back to hiccup for the technical diagram
[:figure {:caption "System architecture overview." :label :fig/arch}
[:image {:src "architecture.png" :width "80%"}]]
#t"
Our method works by first processing the input through
the encoder (see @fig:arch). The encoded representation
is then passed to the decoder which produces the output.
= Results
"
;; Programmatic table
(results-table results)
#t"
As shown in the table above, our method significantly
outperforms the baseline.
= Conclusion
We have demonstrated that our approach is ~(if (> (:accuracy (last results)) 0.9)
"highly effective"
"somewhat effective").
Future work will explore extensions to other domains.
"
;; Bibliography could be generated
[:bibliography {:src "refs.bib"}]])
;; Compile it
(compile-to-typst paper "paper.typ")
```
## Macros
It's a Lisp - of course we have macros.
### Standard Code Macros
```clojure
;; Threading for cleaner data transforms
(defmacro -> [x & forms]
...)
(-> data
(filter :active)
(map :name)
(sort))
;; Custom control flow
(defmacro when-let [[binding expr] & body]
`(let [~binding ~expr]
(when ~binding ~@body)))
```
### Content Macros
Macros that expand to hiccup/content:
```clojure
;; Define a reusable content pattern
(defmacro defcomponent [name args & body]
`(defn ~name ~args
(list ~@body))) ; sequences flatten into parent content
;; Conditional content sections
(defmacro when-content [test & body]
`(when ~test
(list ~@body)))
;; Use it
(when-content show-abstract?
[:heading {:level 2} "Abstract"]
abstract-text)
```
### Document Structure Macros
```clojure
;; Academic paper structure
(defmacro paper [& {:keys [title authors abstract] :as opts} & sections]
`[[:heading {:level 1} ~title]
(author-block ~authors)
(when ~abstract
(list
[:heading {:level 2} "Abstract"]
~abstract))
~@sections])
;; Usage
(paper
:title "My Great Paper"
:authors ["Alice" "Bob"]
:abstract "We did cool stuff."
[:heading {:level 2} "Introduction"]
"..."
[:heading {:level 2} "Methods"]
"...")
```
### DSL Macros
Build mini-languages for specific domains:
```clojure
;; Math DSL that's easier to write than raw Typst math
(defmacro math-block [& exprs]
`[:math {:block true}
~(compile-math-dsl exprs)])
(math-block
(sum :i 0 :n
(frac (pow x i) (factorial i))))
;; => $ sum_(i=0)^n x^i / i! $
;; Table DSL
(defmacro deftable [name headers & row-specs]
...)
(deftable results-table
["Method" "Precision" "Recall" "F1"]
:from results
:row (fn [{:keys [method p r f1]}]
[method (fmt p) (fmt r) (fmt f1)]))
;; Figure DSL with auto-numbering
(defmacro figure [id & {:keys [src caption width]}]
`(do
(register-figure! ~id)
[:figure {:label ~id :caption ["Figure " (figure-num ~id) ": " ~caption]}
[:image {:src ~src :width ~(or width "100%")}]]))
(figure :arch
:src "architecture.png"
:caption "System overview"
:width "80%")
```
### Syntax Extension Macros
```clojure
;; Custom reader syntax for common patterns
;; (would require reader macro support)
;; Shorthand for references
(defmacro ref [id]
`[:ref {:target ~id}])
;; Shorthand for citations
(defmacro cite [& keys]
`[:cite {:keys [~@keys]}])
;; Usage
(ref :fig/arch) ; => [:ref {:target :fig/arch}]
(cite :smith2020 :jones2021) ; => [:cite {:keys [:smith2020 :jones2021]}]
```
### Template Macros
```clojure
;; Define document templates
(defmacro deftemplate [name args & structure]
`(defmacro ~name ~args
~@structure))
(deftemplate ieee-paper [title authors abstract]
[[:set {:element :page :paper "us-letter" :columns 2}]
[:heading {:level 1 :align "center"} ~title]
(render-authors ~authors)
(render-abstract ~abstract)
~'&body]) ; splice remaining content
;; Use template
(ieee-paper
"Neural Networks for Fun and Profit"
[{:name "Alice" :affil "MIT"}]
"We present..."
[:heading {:level 2} "Introduction"]
"..."
[:heading {:level 2} "Methods"]
"...")
```
### Why Macros Matter Here
| Pattern | Without Macros | With Macros |
|---------|---------------|-------------|
| Repeated structure | Copy-paste hiccup | `defcomponent` |
| Conditional sections | Verbose `if` expressions | `when-content` |
| Domain languages | Manual compilation | DSL macros |
| Document templates | Function composition | Template macros |
| Boilerplate elimination | Nowhere to hide it | Macro it away |
**The power:** Your document language grows to fit your domain.
## Rules
1. **`#t"..."`** - raw Typst content, returned as-is
2. **`~(...)`** inside raw - interpolate Clojure expression (like unquote)
3. **`[:tag ...]`** - hiccup, compiled to Typst function calls
4. **Strings in hiccup** - become Typst content directly
5. **Seqs in hiccup** - flattened (so `map`/`for` just work)
6. **Sequences flatten** - `(for ...)`, `(map ...)`, `(list ...)` results merge into parent
## Why Both?
| Situation | Use |
|-----------|-----|
| Prose, paragraphs, natural writing | Raw `#t"..."` |
| Tables, figures, structured layout | Hiccup |
| Loops, conditionals, data-driven | Hiccup or interpolated raw |
| Components, reusable parts | Hiccup functions |
| Quick one-off formatting | Either, your preference |
**The principle:** Structure when you need it, prose when you don't.