Est. 1987 Advanced

Clean

The purely functional graph-rewriting language from Radboud University whose uniqueness type system made destructive updates and real-world I/O safe without monads.

Created by Rinus Plasmeijer, Marko van Eekelen, and the Software Technology Research Group at Radboud University Nijmegen

Paradigm Functional (pure, lazy), Graph rewriting
Typing Static, Strong, Inferred, Uniqueness types
First Appeared 1987
Latest Version Clean 3.1 (January 5, 2022)

Clean is a purely functional, lazy programming language developed since 1987 by the Software Technology Research Group at Radboud University Nijmegen in the Netherlands. It looks, at a glance, like a cousin of Haskell – and it is – but it rests on two ideas that set it apart from every other language of its generation: computation is defined as graph rewriting rather than term rewriting, and side effects are made safe by a uniqueness type system rather than by monads. That second idea, invented for Clean, is the ancestor of the linear and ownership types that later reached far larger audiences.

History & Origins

From parallel graph rewriting to a language

In the mid-1980s, the Nijmegen group led by Rinus Plasmeijer was working on the Dutch Parallel Reduction Machine project, investigating how to execute functional programs efficiently on parallel hardware. That work produced Lean, an experimental intermediate language based on term graph rewriting – a computational model in which programs manipulate graphs rather than trees of terms, so that sharing of subexpressions is explicit and controllable rather than an accident of the implementation.

Clean grew directly out of that research. Development began in 1987, and the system was originally distributed as the Concurrent Clean System, reflecting its origins in parallel evaluation. Where most functional languages treat their internal graph representation as an implementation detail, Clean elevated it into the semantics: the language is defined by rewriting a graph, and the programmer can reason about sharing and copying because those things are visible in the model.

Plasmeijer and Marko van Eekelen documented the theory at length in Functional Programming and Parallel Graph Rewriting (Addison-Wesley, 1993), which remains the standard reference for Clean’s foundations.

Maturity and the Clean 2 era

Clean 1.0, released in May 1995, was the first version considered mature enough for general use. Over the following years the language accumulated a native-code compiler for several platforms, an integrated development environment on Windows, and the ObjectIO library for building graphical applications in a purely functional style.

The Concurrent Clean Language Report version 2.0, published by Plasmeijer and van Eekelen in December 2001 (revised as version 2.1 in November 2002), defined the Clean 2 language. This is the specification most Clean code still targets, and Clean 2 brought the language closer to Haskell in surface syntax – type classes, records, and a module system that a Haskell programmer can read – while keeping uniqueness typing and graph rewriting at its core.

Task-Oriented Programming

The most consequential thing built in Clean arrived in 2007, when Plasmeijer, Peter Achten, and Pieter Koopman presented iTasks at ICFP in Freiburg. iTasks is a combinator library for specifying interactive, multi-user workflow systems; from a declarative description of what work needs doing, by whom, and in what order, the system derives the web interface, the data storage, and the coordination between participants. It became the seed of an entire research programme – Task-Oriented Programming, or TOP – that continues at Radboud today.

Design Philosophy

Clean is built on a small number of commitments, held more strictly than in most languages:

  • Purity is non-negotiable. Every function is referentially transparent. There is no escape hatch.
  • Effects are a typing problem, not a plumbing problem. Rather than sequencing effects through a monad, Clean tracks in the type system whether a value is uniquely referenced, and permits destructive update exactly when it is.
  • The graph is part of the language. Sharing, cycles, and lazy nodes are semantic entities the programmer can talk about, not compiler internals.
  • Efficiency is a design goal, not an afterthought. Clean compiles to native machine code, and the uniqueness information the type checker computes is fed directly to the code generator so that unique data structures can be updated in place instead of copied.

Key Features

Uniqueness typing

This is Clean’s signature contribution. A type annotated with * is unique: the compiler has proven that at that point in the program, only one reference to the value exists. Because nobody else can observe it, the runtime is free to overwrite it destructively, and the program remains referentially transparent.

The world itself is a unique value. A Clean program’s entry point receives the world and returns a new one:

module hello

import StdEnv

Start :: *World -> *World
Start world
    # (console, world) = stdio world
      console          = fwrites "Hello, World!\n" console
      (_, world)       = fclose console world
    = world

Each step consumes the unique *World (or *File) and produces a new one. Using the old world twice would be a type error, which is precisely what prevents the program from observing the effect of a destructive update. The # notation is Clean’s let-before construct, designed for exactly this style: it lets a name be rebound in sequence, so the threading of unique state reads like a sequence of statements while remaining pure function application underneath.

Arrays and files work the same way. A unique array can be written in place with no copying, which is why Clean can express array-heavy numerical code without giving up purity.

Graph rewriting semantics

Clean’s evaluation model is term graph rewriting. A definition such as

ones :: [Int]
ones = [1 : ones]

builds a genuinely cyclic graph rather than an infinitely unfolding tree. Because sharing is explicit in the model, the programmer can reason about which computations are performed once and which are repeated – a level of control that lazy languages built on term rewriting tend to leave to the implementation’s whims.

Lazy by default, strict where you ask

Clean is lazy, but strictness is a first-class annotation rather than a pragma bolted on:

:: Point = { x :: !Real, y :: !Real }

sum :: !Int ![Int] -> Int
sum acc []     = acc
sum acc [x:xs] = sum (acc + x) xs

Strictness annotations on function arguments and record fields let the compiler avoid building thunks entirely, which matters both for performance and for avoiding space leaks in accumulating loops.

Familiar functional machinery

Everything a functional programmer expects is present: algebraic data types, pattern matching, guards, higher-order functions, currying, type classes, and list comprehensions. Clean’s comprehension syntax uses \\ where Haskell uses |:

primes :: [Int]
primes = sieve [2..]
where
    sieve [p:xs] = [p : sieve [x \\ x <- xs | x rem p <> 0]]

Note also that Clean writes cons patterns as [x:xs] rather than (x:xs), and uses :: for type definitions where Haskell uses data.

Generic programming and dynamics

Clean was an early adopter of generic (datatype-generic) programming, where a function is defined once over the structure of types and then derived for any concrete type:

:: Tree a = Node a (Tree a) (Tree a) | Leaf

derive gEq   Tree
derive gPrint Tree

The related dynamics feature allows values – including functions – to be packaged with their types, serialized, shipped to another machine or another program, and type-checked on arrival. This is what makes iTasks able to move live computation between server and client.

The ABC machine

Clean compiles through an intermediate abstract machine called the ABC machine, an imperative model with three stacks – the A(rgument) stack, the B(asic value) stack, and the C(ontrol) stack – plus a graph store. Native code generators translate ABC code to machine code. Keeping the ABC layer as a well-defined intermediate representation later paid off unexpectedly: in 2019, Camil Staps, John van Groningen, and Plasmeijer presented an ABC bytecode interpreter at IFL that runs in WebAssembly, so Clean expressions can be serialized and evaluated in a web browser alongside natively compiled code.

Evolution

MilestoneWhenSignificance
Development begins1987Grows out of Lean and parallel graph rewriting research at Nijmegen
Clean 1.0May 1995First mature release
Language Report 2.0December 2001Defines the Clean 2 language
Language Report 2.1November 2002Refined Clean 2 specification; the 2.1 release followed in October 2003
iTasks at ICFP2007Task-Oriented Programming begins
Clean 2.4December 23, 2011Last of the widely used 2.x line
Clean 3.0October 2, 2018Major version bump
Clean 3.1January 5, 2022Current stable release

The pattern is characteristic of a research language: long, quiet stretches of stability punctuated by releases that consolidate years of work. The interesting motion since 2018 has been less in the core language than around it – the WebAssembly backend, the continuing iTasks line, and a modernised distribution story.

Current Relevance

The Clean System is distributed for Windows, Linux, and macOS; according to the official distribution, builds are offered for 32- and 64-bit Windows, 32- and 64-bit Intel Linux, 64-bit ARM Linux, and 64-bit Intel macOS. Availability of any particular build varies between releases, so it is worth checking the download page rather than assuming a target is covered. Alongside the classic distribution, the Clean and iTasks developers maintain clean-lang.org, a package registry paired with Nitrile, a package manager and build tool for Clean that resolves dependencies and drives builds in the style modern developers expect. Notably, the 3.1 stable release is reportedly not the version used with iTasks – iTasks development tracks the newer Clean releases distributed through that registry, which is worth knowing before installing.

Clean’s practical user base is small and centred on Radboud University and its collaborators. Activity is concentrated in the Task-Oriented Programming research line, the compiler and runtime, and teaching. Papers such as Clean for Haskell Programmers (Mart Lubbers and Peter Achten, 2024) exist precisely because the natural on-ramp to Clean today is from Haskell, and because the community is small enough that a short translation guide is the efficient way to bring newcomers in.

Why It Matters

Clean’s importance is out of all proportion to the number of people who write it.

It solved the effects problem differently, and first. When the lazy functional programming community was wrestling with how a pure language could do I/O, Clean’s answer was uniqueness typing – track the number of references in the type system, and destructive update becomes safe. Haskell went the monadic route and won the popularity contest, but Clean’s answer turned out to be the more general idea. Uniqueness types are the direct model for Idris’s uniqueness types, and the broader family of substructural type systems that Clean helped pioneer underlies the ownership and borrowing disciplines that later languages use to guarantee memory safety without a garbage collector.

It showed that purity need not cost performance. By feeding uniqueness information from the type checker into the code generator, Clean compiles purely functional array and file manipulation into in-place updates on native code. The argument that “pure means copying” was refuted by a working compiler decades ago.

It kept graph rewriting honest. Most lazy languages have a graph in the implementation and a tree in the semantics. Clean put the graph in the semantics, giving programmers a genuine handle on sharing.

Task-Oriented Programming is a real idea. iTasks demonstrated that a distributed, multi-user, interactive system can be specified declaratively – as a composition of tasks – and the interface, storage, and coordination derived rather than written. That line of work has drawn interest from Dutch defence-affiliated researchers – the Netherlands Coast Guard incident coordination prototypes came out of a collaboration involving the Netherlands Defence Academy – precisely because it shortens the path from a description of how coordination should work to a running system.

Clean is dormant in the sense that matters to industry: you will not find job postings for it, and its release cadence is measured in years. But it is very much alive in the sense that matters to language design. Its central invention escaped the language and is now load-bearing infrastructure elsewhere – which is arguably the most any research language can hope for.

Timeline

1987
Development of Clean begins at the University of Nijmegen (now Radboud University), growing out of the Software Technology Research Group's work on parallel graph rewriting and the intermediate language Lean
1993
Rinus Plasmeijer and Marko van Eekelen publish "Functional Programming and Parallel Graph Rewriting" (Addison-Wesley), the book-length account of the theory behind Clean
1995
Clean 1.0 released in May as the first mature version of the language, distributed as the Concurrent Clean System
2001
Concurrent Clean Language Report version 2.0 published in December 2001 by Plasmeijer and van Eekelen, introducing the Clean 2.x line
2002
Clean Language Report version 2.1 published in November, refining the Clean 2 specification; the corresponding Clean 2.1 release followed on October 31, 2003
2007
iTasks presented at ICFP 2007 in Freiburg by Plasmeijer, Achten, and Koopman, launching Task-Oriented Programming as a Clean-based approach to workflow systems
2011
Clean 2.4 released on December 23
2018
Clean 3.0 released on October 2
2019
Staps, van Groningen, and Plasmeijer present an ABC bytecode interpreter at IFL 2019 that runs Clean code in WebAssembly, allowing Clean expressions to be serialized and evaluated in the browser
2022
Clean 3.1 released on January 5, the current stable version

Notable Uses & Legacy

iTasks and Task-Oriented Programming

The iTasks framework, written in Clean, specifies multi-user, distributed workflow applications as composable tasks and generates the web interface, persistence, and coordination logic automatically. It remains the flagship demonstration of what Clean's type system makes possible.

Netherlands Coast Guard incident coordination (Incidone)

Bas Lijnse, Jan Martin Jansen, and Rinus Plasmeijer modelled the Netherlands Coast Guard's search-and-rescue workflow in iTasks and built Incidone, a task-oriented incident coordination tool written in Clean, presented at ISCRAM 2012. Lijnse and Jansen are affiliated with the Netherlands Defence Academy, and the work is the most concrete applied use of Task-Oriented Programming outside the university.

Radboud University teaching and research

Clean is the vehicle for functional programming courses and a long line of PhD research at Radboud University Nijmegen, covering uniqueness typing, generic programming, dynamic types, and compiler technology.

The Clean compiler itself

The Clean compiler, code generator, and much of its tooling are written in Clean, making the system a substantial self-hosted demonstration of the language handling state, file I/O, and code generation in a purely functional setting.

Programming language research on uniqueness and linear types

Clean is the canonical reference implementation of uniqueness typing, cited across the literature on linear types, ownership, and safe in-place update, and directly credited as the model for uniqueness types in Idris.

Language Influence

Influenced By

Lean Miranda Haskell

Influenced

Running Today

Run examples using the official Docker image:

docker pull
Last updated: