a first-order language · evaluation as σ-threading · sample & observe in the evaluator · likelihood weighting = importance sampling
2026-06-24
\[ \newcommand\given{{\,\vert\,}} \]
Learning Objectives
sample + observe + statically bounded loops, so the set of random variables is finite and known before running.sample draws from the prior and moves on; observe draws nothing and adds \(\log p_d(y)\) to \(\sigma\).What we did
We saw why exact rejection fails for continuous observations: matching a value such as \(y = 2.3\) has probability zero. The escape is to score executions instead of filtering them.
eval that threads a state \(\sigma\).observe” into a real, general algorithm: likelihood weighting.The book introduces two core language families.
| language | random-variable cardinality | typical restrictions |
|---|---|---|
| FOPPL | statically bounded | no recursion; bounded loops; first-order functions |
| HOPPL | not statically bounded | recursion; higher-order functions; dynamic control flow |
The important distinction is not just first-order vs higher-order. It is whether the number of random variables is statically bounded.
In designing these languages our aim, however, was to establish the difference between finite random variable cardinality languages (FRVCL) and unbounded random variable cardinality languages (URVCL). These acronyms are terrible.
| statically bounded | not statically bounded |
|---|---|
| BUGS, JAGS, Stan | Church, Anglican, WebPPL, Venture |
| model structure fixed before execution | model structure may depend on execution |
| FOPPL is the toy language for this case | HOPPL is the toy language for this case |
Today we start with the statically bounded case: FOPPL.
Four candidate programs. Three lay out a fixed set of random variables before running; one does not.
Which one is NOT a legal FOPPL program?
A) Program 🍦: it recurses until a random event, so the number of sample calls is not bounded before running.
B) Program 🥗: observe may not appear as a statement whose value is discarded.
C) Program 🍕: if is not part of the first-order language.
D) All four are legal: every one parses as a FOPPL expression.
A Lisp variant (Clojure-flavored). Eight expression forms, six ordinary and two probabilistic:
let, if, user/primitive calls. This is an ordinary deterministic language.(sample d): draw a value from distribution d. (observe d v): declare v was drawn from d.The defining property
With those restrictions, every program describes a finite set of random variables, and that set can be determined before the program runs.
foreach and loop take a constant iteration count, not an expression:
let forms at compile time.Sugar, set aside
let with many bindings, foreach, loop, vector and map literals are readability sugar that desugars to the eight core forms. We build the core evaluator, not the surface syntax.
A legal FOPPL program: one latent mean, three observations in a bounded loop.
How many latent random choices are sampled during one execution?
A) Four: the latent mu, plus one random variable for each of the three observe forms.
B) One: only mu is a random variable; the three observe forms condition on fixed data, they add no latent variables.
C) Three: the foreach runs three times, so it creates three random variables.
D) Unknown until the program runs and the loop actually executes.
Strip away sample and observe and the FOPPL is an ordinary expression evaluator over an environment \(\rho\):
let extends \(\rho\): evaluate e1 to a value, bind it, evaluate the body in the extended \(\rho\).if evaluates one branch (lazy). Calls bind arguments in a fresh \(\rho\).let is evaluated.The deterministic core comes for free
Everything except sample and observe is just a small interpreter. The probabilistic content lives in two forms and in the state those forms touch.
We are about to describe what one run does. Keep three things separate:
| layer | what it is | example |
|---|---|---|
| object language | the FOPPL program being run | (observe (normal x 1) 2.3) |
| semantic notation | how we describe the evaluator | \(\rho, \ell, \sigma, e \Downarrow v, \sigma'\) |
| implementation | how we code it | a Python dict { "logW": ... } |
The judgment reads: in global environment \(\rho\), local environment \(\ell\), and starting inference state \(\sigma\), expression \(e\) evaluates to value \(v\) and produces an updated inference state \(\sigma'\).
σ is the inference state
\(\rho\) stores global definitions; \(\ell\) stores local variable bindings. \(\sigma\) is the inference state, threaded through every evaluation. The notation is not part of the language; it is how we study the evaluator. For likelihood weighting, \(\sigma\) holds one number: \(\log W\).
Run forward, threading \(\sigma\), with \(\sigma\) initialized to \(\log W = 0\):
(sample d): draw \(x \sim d\) (from the prior) and continue. The draw is a random choice the run made.(observe d v): draw nothing. Add \(\log p_d(v)\) to \(\sigma\)’s \(\log W\), return \(v\), continue.sample moves, observe scores
sample draws. observe scores. Both return a value and evaluation continues.
One run by hand worked for one program. Here is the general evaluator: a single recursive function that threads the inference state state (it carries the rng and the running log_w). Four forms are special; everything else is a primitive call.
In your notebook, complete the if case so the evaluator runs. Hint: if is lazy and threads state like every other form, evaluate the test, then evaluate only the branch it selects.
The FOPPL
Ordinary expressions + sample + observe + bounded loops. No recursion, no higher-order functions, so the set of random variables is finite and known before running. Loops are finite expansions.
Evaluation threads σ
One execution gives a return value, the random choices made, and a log weight in the inference state \(\sigma\), described by \(\rho, \ell, \sigma, e \Downarrow v, \sigma'\). sample draws from the prior and moves on; observe draws nothing and adds \(\log p_d(y)\).
Reading
Before next class, read:
Focus while reading
Pay attention to:
Solutions
sample calls run-dependent, which FOPPL forbids; observe-as-statement (🥗) and if (🍕) are both legal core forms.sample site mu is a latent random variable; the three observes condition on fixed data and add none, and the count is fixed before running because foreach has a constant bound. A counts observes as variables, C counts loop iterations as new variables, D forgets that the structure is static.Introduction to Probabilistic Programming