← Back to Architecture & Comparison Guides
Published: 2026-10-23 Updated: 2026-10-23 Source: bend2.org

Bend 2 vs. Haskell: GHC Lazy STG Thunks & Accelerate vs. Strict Affine Evaluation and 0.38 s Proofs

An architectural comparison of GHC Haskell (par/pseq, Strategies, Accelerate, LiquidHaskell) and Bend 2 (BendTT affine dependent types and the BendRT 128x128 Task Cube).


Executive Summary: Bottom Line Up Front

Haskell and Bend 2 share pure functional DNA (algebraic datatypes, pattern matching, referential transparency, and an explicit IO monad). However, Bend 2 discards GHC’s lazy Spineless Tagless G-machine (STG) and tracing garbage collector in favor of strict affine evaluation (BendTT) and native CPU/GPU compilation (BendRT).

  1. Choose Haskell (GHC 9.10, Cabal/Stack) for complex compiler toolchains (such as Pandoc), domain-specific financial contracts, parser combinators (Megaparsec), and lazy infinite stream pipelines backed by Hackage’s three decades of typeclass libraries.
  2. Choose Bend 2 (bend -o, LAWS.bend, PROOF.bend) when your functional program requires predictable memory footprints (2.0 MB to 2.8 MB RSS with zero space leaks), automatic GPU offloading (f!(x)), or built-in inductive theorem proving (0.38 s check time). GHC’s par/pseq sparks are confined to CPU threads and Accelerate is restricted to flat array DSLs. Bend 2 compiles recursive algebraic trees directly to 16 CPU cores and Metal/CUDA GPU warps for up to 123.9x GPU speedup (benchmark dataset).

1. Runtime Architecture: GHC Lazy STG Thunks vs. BendRT 64-Bit Affine Words

Why has pure functional programming historically struggled to run on GPUs? The obstacle in GHC Haskell is lazy evaluation and tracing garbage collection:

Runtime Dimension GHC Haskell (STG Machine + Generational GC) Bend 2 (BendTT + BendRT Worklist Machine)
Evaluation Strategy Call-by-need (lazy by default). Unevaluated expressions allocate heap thunks that mutate in-place (BLACKHOLE) upon forcing. Strict affine evaluation with whole-program Share and Borrow inference (source).
Memory Layout & GC Tracing generational garbage collector; lazy fold accumulation (foldl) notoriously causes heap space leaks. Zero tracing GC: every term is a 64-bit word ([rfc | tag | aux | loc]), and pattern matching frees consumed nodes immediately (heap_free) (source).
CPU Spark Parallelism Control.Parallel.Strategies (par, pseq) schedules sparks on the GHC RTS CPU thread pool; thunk locking can stall threads. Parallel let (a b = f(x) g(y)) deals tasks into the 128 × 128 Task Cube (16,384 lock-free rings) with zero work stealing (source).
GPU Execution Requires embedded array DSLs (Data.Array.Accelerate); cannot compile arbitrary recursive ADTs (data Tree a) to GPU kernels. Compiles recursive ADTs directly to Metal and CUDA shaders from the same .bend file (source).

2. Formal Verification: LiquidHaskell & Type-Level GADTs vs. LAWS.bend & PROOF.bend

In Haskell, proving properties about functions requires either encoding invariants via singleton types (DataKinds, GADTs, TypeFamilies) or using an external SMT refinement checker like LiquidHaskell:


3. Apple M4 Max Benchmarks: Pure Functional Algorithms at C and GPU Speed

Across the 16 functional benchmarks in /data/bend-ecosystem.json (written using pure algebraic recursion without mutable loops), Bend 2’s single-core SEQ-CPU matches -O3 C within 0.70x to 1.50x on 14 workloads while scaling across 16 CPU cores and GPU warps (benchmark dataset):

Workload Category Native C (-O3) Bend SEQ-CPU (RSS) Bend PAR-CPU 16c (RSS) Bend PAR-GPU Metal GPU Speedup vs. SEQ
Conway’s Game of Life Cellular Automata 6.776 s 7.803 s (2.1 MB) 0.647 s (2.7 MB) 0.063 s 123.9x (source)
N-Body Physics Particle Gravity 5.126 s 5.758 s (2.2 MB) 0.516 s (2.7 MB) 0.059 s 97.6x (source)
Merkle Tree Cryptographic Trees 3.423 s 4.222 s (130 MB) 0.422 s (131 MB) 0.071 s 59.5x (source)
3D Raytracer Computer Graphics 3.646 s 4.616 s (2.1 MB) 0.396 s (2.6 MB) 0.151 s 30.6x (source)
Tree Bitonic Sort Sorting Networks 5.719 s 6.043 s (146 MB) 0.799 s (156 MB) 0.450 s 13.4x (source)
Tree Matrix Multiply Quadtree Algebra 2.964 s 2.064 s (2.8 MB) 0.226 s (14.8 MB) 0.231 s 8.9x (source)
Symbolic Regression Genetic AST Search 2.208 s 3.014 s (2.1 MB) 0.266 s (3.1 MB) 0.529 s 5.7x (source)

For Haskell engineers who love equational reasoning (reverse (reverse xs) == xs) and algebraic data types, Bend 2 makes those equations executable compiler laws (LAWS.bend). It then runs the resulting functional trees across 16,384 GPU lanes with zero space leaks (benchmark dataset).


4. Side-by-Side Code: GHC par/pseq Sparks vs. Bend 2 Parallel Let & Equational Proof

In GHC Haskell, parallelizing a recursive tree reduction requires Control.Parallel (par and pseq) to force lazy thunks on the CPU runtime, and cannot compile recursive Tree ADTs to GPU kernels:

-- GHC Haskell: CPU-only spark parallelism with explicit strictness forcing
import Control.Parallel (par, pseq)

data Tree = Leaf ! Word32 | Node !Tree !Tree

sumTree :: Tree -> Word32
sumTree (Leaf x)   = x
sumTree (Node l r) = a `par` (b `pseq` (a + b))
  where
    a = sumTree l
    b = sumTree r

In Bend 2, evaluation is strict and affine by default, a b = sum(lft) sum(rgt) populates the 128 × 128 Task Cube for CPU or GPU execution, and equational theorems (mirror_involutive) are checked directly by the compiler in 0.38 s (bendlang/bend):

type Tree:
  case @Leaf:
    val: U32
  case @Node:
    lft: Tree
    rgt: Tree

def sum(t: Tree) -> U32:
  match t:
    case @Leaf{val}:
      val
    case @Node{lft, rgt}:
      a b = sum(lft) sum(rgt)
      a + b

def mirror(t: Tree) -> Tree:
  match t:
    case @Leaf{val}:
      @Leaf{val}
    case @Node{lft, rgt}:
      l r = mirror(rgt) mirror(lft)
      @Node{l, r}

def mirror_involutive(t: Tree) -> Tree{mirror(mirror(t)) == t}:
  match t:
    case @Leaf{val}:
      finally
    case @Node{lft, rgt}:
      ih_l = mirror_involutive(lft)
      ih_r = mirror_involutive(rgt)
      rewrite ih_l
      rewrite ih_r
      finally