Static effect inference for Clojure — no type system required.
Beichte (German: to confess) analyzes Clojure code via tools.analyzer.jvm and infers side-effect levels on a four-point lattice:
:pure < :local < :mutation < :io
Designed for compiler builders: validate that code is safe for AD, GPU compilation, parallelization, or CSE before transforming it.
Quick start:
(require '[beichte.core :as b])
;; Analyze expressions (fresh analysis each call — safe for REPL) (b/analyze '(+ 1 2)) ;; => :pure (b/analyze '(swap! a inc)) ;; => :mutation (b/analyze '(println "hi")) ;; => :io (b/analyze '(aset arr 0 42)) ;; => :local
;; Full descriptors with feature flags (b/analyze-full '(throw (Exception. "x"))) ;; => {:effect :pure, :flags #{:throws}}
;; Check if code is safe for a compilation target (b/compilable? :gpu '(+ 1 2)) ;; => true (b/compilable? :gpu '(throw (Exception. "x"))) ;; => false
;; Compiler pipelines: create a context to cache across calls (let [ctx (b/make-context)] (b/analyze '(foo x) ctx) ;; analyzes foo from source (b/analyze '(bar (foo x)) ctx) ;; foo already cached (b/analyze-var #'baz ctx)) ;; works for vars too
See beichte.effect for the lattice and flags, beichte.registry for the pre-annotated clojure.core entries, and beichte.analyze for the AST-walking inference engine.
Static effect inference for Clojure — no type system required.
Beichte (German: to confess) analyzes Clojure code via tools.analyzer.jvm
and infers side-effect levels on a four-point lattice:
:pure < :local < :mutation < :io
Designed for compiler builders: validate that code is safe for AD,
GPU compilation, parallelization, or CSE before transforming it.
Quick start:
(require '[beichte.core :as b])
;; Analyze expressions (fresh analysis each call — safe for REPL)
(b/analyze '(+ 1 2)) ;; => :pure
(b/analyze '(swap! a inc)) ;; => :mutation
(b/analyze '(println "hi")) ;; => :io
(b/analyze '(aset arr 0 42)) ;; => :local
;; Full descriptors with feature flags
(b/analyze-full '(throw (Exception. "x")))
;; => {:effect :pure, :flags #{:throws}}
;; Check if code is safe for a compilation target
(b/compilable? :gpu '(+ 1 2)) ;; => true
(b/compilable? :gpu '(throw (Exception. "x"))) ;; => false
;; Compiler pipelines: create a context to cache across calls
(let [ctx (b/make-context)]
(b/analyze '(foo x) ctx) ;; analyzes foo from source
(b/analyze '(bar (foo x)) ctx) ;; foo already cached
(b/analyze-var #'baz ctx)) ;; works for vars too
See beichte.effect for the lattice and flags, beichte.registry for
the pre-annotated clojure.core entries, and beichte.analyze for the
AST-walking inference engine.(analyze expr)(analyze expr ctx-or-opts)Infer the effect level of a Clojure expression.
Returns one of: :pure, :local, :mutation, :io
Without context: fresh analysis each call (safe for REPL). With context: reuses cached var analysis results.
Infer the effect level of a Clojure expression. Returns one of: :pure, :local, :mutation, :io Without context: fresh analysis each call (safe for REPL). With context: reuses cached var analysis results.
(analyze-full expr)(analyze-full expr ctx-or-opts)Infer the full effect descriptor of a Clojure expression.
Returns {:effect :level, :flags #{...}} where flags are orthogonal properties like :throws, :random, :reflects, :allocates.
Infer the full effect descriptor of a Clojure expression.
Returns {:effect :level, :flags #{...}} where flags are orthogonal
properties like :throws, :random, :reflects, :allocates.(analyze-ns ns-sym)(analyze-ns ns-sym ctx-or-opts)Analyze all public vars in a namespace.
Returns {var → effect-level}.
Analyze all public vars in a namespace.
Returns {var → effect-level}.(analyze-ns-full ns-sym)(analyze-ns-full ns-sym ctx-or-opts)Analyze all public vars and return {var → descriptor}.
Analyze all public vars and return {var → descriptor}.
(analyze-var v)(analyze-var v ctx-or-opts)Infer the effect level of a var by analyzing its source.
Returns one of: :pure, :local, :mutation, :io
Infer the effect level of a var by analyzing its source. Returns one of: :pure, :local, :mutation, :io
(analyze-var-full v)(analyze-var-full v ctx-or-opts)Infer the full effect descriptor of a var.
Returns {:effect :level, :flags #{...}}.
Infer the full effect descriptor of a var.
Returns {:effect :level, :flags #{...}}.(budget-for target)Return the effect budget for a compilation target.
Return the effect budget for a compilation target.
(compilable? target expr)(compilable? target expr ctx-or-opts)True if an expression is within the effect budget for a compilation target.
Checks both state effect budget and feature flag support.
Targets and their budgets: :ad, :cse — :pure (no effects at all) :gpu, :simd — :local (thread-local mutation OK) :parallel — :local :sequential — :io (anything goes) :inline — :io (anything goes)
GPU also rejects :throws and :reflects flags.
True if an expression is within the effect budget for a compilation target. Checks both state effect budget and feature flag support. Targets and their budgets: :ad, :cse — :pure (no effects at all) :gpu, :simd — :local (thread-local mutation OK) :parallel — :local :sequential — :io (anything goes) :inline — :io (anything goes) GPU also rejects :throws and :reflects flags.
(default-registry)Return the default registry with pre-annotated clojure.core and Java entries.
Return the default registry with pre-annotated clojure.core and Java entries.
(extend-registry reg entries)Extend a registry with additional entries.
Extend a registry with additional entries.
(filter-compilable target vars)(filter-compilable target vars ctx-or-opts)Filter vars to those within the effect budget for a compilation target.
Filter vars to those within the effect budget for a compilation target.
Known feature flags: #{:throws :random :reflects :allocates}
Known feature flags: #{:throws :random :reflects :allocates}
(join a b)Least upper bound of two effects.
Least upper bound of two effects.
Effect levels in ascending order: [:pure :local :mutation :io]
Effect levels in ascending order: [:pure :local :mutation :io]
(make-context)(make-context {:keys [registry]})Create an analysis context that caches results across calls.
For REPL use, omit the context — each call analyzes fresh. For compiler pipelines, create one context per pass and reuse it.
Options: :registry — custom effect registry (default: default-registry)
Create an analysis context that caches results across calls. For REPL use, omit the context — each call analyzes fresh. For compiler pipelines, create one context per pass and reuse it. Options: :registry — custom effect registry (default: default-registry)
(make-registry entries)Create a custom registry from a map of {entry → effect-level}. Entries can be vars, [class method] pairs, or class name strings.
Create a custom registry from a map of {entry → effect-level}.
Entries can be vars, [class method] pairs, or class name strings.(within-budget? budget eff)True if effect level is at most as effectful as budget.
True if effect level is at most as effectful as budget.
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |