A Julia frontend for writing and composing Stan models
StanCon 2026 · Uppsala
18 August 2026
Write one model in a restricted, Julia-flavoured language:
StanBlocks is a frontend, not a replacement for Stan.
… with quality-of-life improvements such as activity analysis, submodels, user-defined types (currently enabling e.g. NamedTuples and ragged data structures), higher-order user-defined functions, closures, keyword & default arguments, automatic but optional type & shape inference, (ragged) plates, metaprogramming, Julia-style multiple dispatch, and more.
Why put a language in front of an already good language?
Julia maximizes expressive freedom; Stan narrows the problem to make differentiable programs predictable.
Out of the box. Not always the theoretical optimum—but mature vectorisation, fused kernels, reverse-mode AD, and NUTS adaptation make “write the obvious model” a strong baseline.
Static checks, explicit constraints and Jacobians, a mature sampler, and years of use on difficult models.
The generated target is a small, explicit language designed around Bayesian computation rather than general-purpose execution.
stan_code(model) is the boundary between a compositional authoring language and a mature inference language — read it, diff it, archive it, check it with stanc, hand it to the Stan ecosystem.
“Generated” does not have to mean “opaque.”
A small declarative model surface with a surprisingly high ceiling.
What is missing?
Main caveat — model-level control flow: keep @slic flat; use @deffun or plate.
data {
int x_n; vector[x_n] x;
int y_n; vector[y_n] y;
}
parameters {
real alpha; real beta;
real<lower=0> sigma;
}
transformed parameters {
vector[x_n] mu = alpha + beta * x;
}
model {
alpha ~ normal(0, 2);
beta ~ normal(0, 1);
sigma ~ exponential(1);
y ~ normal(mu, sigma);
}
generated quantities {
vector[y_n] y_likelihood = ...;
vector[y_n] y_gen = ...;
}data · transformed data · parameters · transformed parameters · model · generated quantities
types · shapes · constraints · function signatures · captured dimensions
pointwise log likelihood · posterior predictive draws · executable model descriptor
Emits a prefixed parameter eta_beta.
The payoff grows with a family of bespoke models, not with the first ten-line model.
Julia syntax on the front, SlicStan’s information-flow idea in the middle, Stan on the back.
StanBlocks is directly inspired by Gorinova, Gordon & Sutton (POPL 2019). Shared ideas: no author-written Stan blocks, placement inferred from information flow, composable model fragments, and Stan preserved as the inference target.
| SlicStan | StanBlocks.jl (MlicStan?) | |
|---|---|---|
| Frontend | A new Stan-like language | A restricted Julia macro DSL |
| Core analysis | Information-flow type system | Forward trace + reverse likelihood tracking |
| Abstraction | Flexible model functions | Julia dispatch, closures, kwargs, macros, @slic submodels |
| Distinctive reach | Formal semantics; marginalizing out discrete parameters | Auto generated quantities, model variants, descriptors, BridgeStan/Julia integration |
| Implementation posture | Research implementation in F# | Julia package used by larger Julia model DSLs |
| Output | Stan | Stan |
Same source. Only the data you bind changes — activity analysis and metadata decide, per statement, where each sampling statement ends up.
Bind no outcome — m(; subject).
Nothing to condition on, so the whole model forward-simulates in generated quantities — mu, tau, alpha, sigma, and y itself.
Bind y observed.
y ~ normal(alpha[subject], sigma) becomes the likelihood; mu, tau, alpha, sigma are fitted, and y_gen is the posterior-predictive replica.
Bind maybecv(:subject, subject).
Taint flows from the grouping into alpha: the per-subject effects leave parameters and re-draw from normal(mu, tau) in generated quantities, while mu, tau, sigma stay fitted.
Yes, but the value proposition changes.
plates, closures, higher-order functions, hardening.
Agents reduce the cost of writing code. StanBlocks reduces the amount of independent model meaning we have to maintain.
Move up a layer without hiding the layer below.
The largest current public (but unregistered) consumer is BayesianRegressionModels.jl: a brms-like formula layer that currently lowers into Stan via StanBlocks.jl, but will target Julia via Turing.jl as well in the future.
Near-term priorities:
JuliaBayes contributors
Nikolas Siccha
Generable Inc.
Penelope Yong
Alan Turing Institute
Peter Thestrup Waade
TNU, ETH Zürich
Ryan Senne
Boston University
Sam Abbott
LSHTM (epinowcast · epiforecasts)
Simon Steiger
Karolinska Institutet
Source-to-emission examples and the honest edges of the current language.
@deffun@slic stays flat; @deffun owns loops, branches, mutation, iteration, and bounded comprehensions.
The function argument is resolved at compile time: StanBlocks emits one specialized Stan function per callee (apply_twice_exp), so no runtime function object ever reaches Stan.
plate is the loop that samplesplate promotes fresh cell variables to outer storage and clones the compiler-owned loop into the Stan blocks that need it.
Essential rewrite:
data { vector[y_obs_n] y_obs; array[...] int y_ii_obs; array[...] int y_ii_mis; }
parameters { real mu; real<lower=0> sigma; }
model { y_obs ~ normal(mu, sigma); }
generated quantities {
vector[y_ii_mis_n] y_mis = normal_vector_rng(y_ii_mis_n, mu, sigma);
vector[...] y = merge_missing(y_obs, y_mis, y_ii_obs, y_ii_mis);
}The usual case adds no missing-value sampler dimensions: imputed values are posterior-predictive generated quantities.
The compiler selects the matching:
For censoring, the emitted density uses tail mass at the threshold atoms and the predictive RNG clamps a base-family draw.
For the regression example:
Consumers do not need a parallel registry of what a model can do.
| Boundary | Current rule |
|---|---|
| Model-level control flow | Keep @slic flat; use @deffun or plate |
plate recursion / scan |
Cells must be independent; use a deterministic recurrence |
| Plate cell shape | Scalars and fixed vectors; no matrix-valued cell result |
| General containers | No general 3-D+ Julia container surface |
| Missing data | Continuous outcomes only; no missing predictors or discrete parameters |
| Ragged observations | Continuous; groupwise log likelihood; no ragged integer draws |
| Arbitrary Julia calls | Register through @deffun/builtins; no opaque auto-transpilation |
| Validation | transpiles is not enough: run stanc and semantic/gradient checks |
A small language is useful only when its “no” is explicit.
Talk slot: 13:40–14:00, Tuesday 18 August 2026, Ångström Laboratory, Uppsala.
stan_code(model) output, distilled: the functions{} helper block and pointwise-likelihood diagnostics are omitted; observation count shown as N. Highlighted lines are what changed between the two bindings.