Functional programming basics

WebPPL is a functional subset of JavaScript

WebPPL looks like JavaScript, but you should treat it as a functional subset of JavaScript: most idiomatic WebPPL programs are expression-oriented and built by composing functions.

This is not only a matter of style. WebPPL supports probabilistic inference by transforming your program internally (for example into continuation-passing style). Those transformations work best—and most predictably—when your code is written in a functional way.

In practice, for probabilistic modeling in WebPPL, the functional style is the only reliable style.

What “functional programming” means here

Functional programming (FP) is a way of writing programs by building and composing functions, rather than by repeatedly mutating variables and data structures.

A practical checklist for WebPPL:

  • Prefer expressions over statements. Write code that returns values instead of code that updates state.

  • Prefer pure functions. A pure function always returns the same result for the same inputs. (Randomness is a deliberate exception in probabilistic programs.)

  • Avoid mutation. Instead of changing an array/object in-place, create a new one.

  • Use higher-order functions. Functions like map, reduce, and repeat are central tools.

A useful mental model

In FP, a program is a pipeline:

  1. Generate values (often by sampling).

  2. Transform them (with functions like map).

  3. Summarize them (with reduce or a custom aggregator).

  4. Return the result.

This is exactly how many WebPPL models are structured.

Key terms (short definitions)

Expression

A piece of code that evaluates to a value (e.g. 1 + 2, flip(0.7), or a function call).

Pure function

A function with no side effects and deterministic behavior: same inputs -> same output.

Higher-order function

A function that takes a function as an argument or returns a function (e.g. map(f, xs) or a function that returns another function).

Closure

A function that “remembers” variables from the scope where it was created.

Immutability

The practice of not modifying data in-place. Instead, create new values.

Why this matters in probabilistic programming

Probabilistic models are naturally described by composition:

  • a prior is a function that produces uncertain values,

  • a likelihood scores or observes data given those values,

  • inference turns the model into a posterior distribution.

This “functions all the way down” structure is exactly what FP encourages.

Common pitfalls (and the FP-friendly alternative)

Mutation-heavy code

Avoid patterns like repeatedly pushing into arrays or updating objects in-place. Prefer building a new list with map/repeat and returning it.

Hidden state

Avoid relying on variables that change across time in complex ways. Prefer passing values explicitly through function arguments.

Deep imperative control flow

Prefer small helper functions and expression-oriented conditionals.

Executable micro-example: closures

This example demonstrates a closure: a function that remembers variables from its environment, and how that plays nicely with map.

// A closure: a function that "remembers" variables from its environment.

var makeAdder = function(a) {
  // The returned function closes over (remembers) 'a'.
  return function(x) {
    return a + x;
  };
};

var add10 = makeAdder(10);

display("add10(5) =");
display(add10(5));

display("map(add10, [1,2,3]) =");
display(map(add10, [1, 2, 3]));

// Another closure example: parameterized threshold check.
var makeThreshold = function(t) {
  return function(x) { return x < t; };
};

var below3 = makeThreshold(3);

display("map(below3, [1,2,3,4,5]) =");
display(map(below3, [1, 2, 3, 4, 5]));

// WebPPL-flavored closure: a function that returns a stochastic function.
var makeBiasedCoin = function(p) {
  return function() { return flip(p); };
};

var coin70 = makeBiasedCoin(0.7);

display("repeat(10, coin70) =");
display(repeat(10, coin70));
node:fs:448
    return binding.readFileUtf8(path, stringToFlags(options.flag));
                   ^

Error: ENOENT: no such file or directory, open '../examples/functional/closures.wppl'
    at Object.readFileSync (node:fs:448:20)
    at main (/home/runner/work/WebpplHelp/WebpplHelp/node_modules/webppl/webppl:121:17)
    at Object.<anonymous> (/home/runner/work/WebpplHelp/WebpplHelp/node_modules/webppl/webppl:161:1)
    at Module._compile (node:internal/modules/cjs/loader:1521:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1623:10)
    at Module.load (node:internal/modules/cjs/loader:1266:32)
    at Module._load (node:internal/modules/cjs/loader:1091:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:164:12)
    at node:internal/main/run_main_module:28:49 {
  errno: -2,
  code: 'ENOENT',
  syscall: 'open',
  path: '../examples/functional/closures.wppl'
}

Node.js v20.20.1

Where to go next

  • If you are new to functions in WebPPL, continue with functions_and_closures.

  • For the most common list-processing patterns, see map_filter_reduce.

  • For WebPPL-specific quirks, see gotchas_js_webppl.