Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Referential Transparency Reloaded

This page gives the Denotational Design interpretation of referential transparency. The concept page covers history and mainstream language usage. Here we focus on what RT means as a design constraint.

In DD, RT is not a purity label. It is a law on denotations: substitution must preserve specified meaning.

Denotational Design Restatement of RT

Denotational Design restates RT as a requirement on specifications, not on implementation style. The specification must define compositional meaning first; execution strategy is chosen later.

So RT is checked at the semantic boundary:

  • same inputs
  • same declared context
  • same denotation

If these hold, substitution is valid and equational reasoning is available. This remains true even when concrete interpreters perform I/O, concurrency, retries, or state transitions, because those details are delegated behind the specified capabilities.

In other words, DD does not ask "is this implementation pure?" DD asks "are the denotational equalities explicit, and are interpreters preserving them?"

Purity And RT

RT and purity are related, but not identical.

  • RT is a substitution law on meaning.
  • purity is an operational constraint: no hidden observable effects.

Purity is a common way to obtain RT for expression-level code. But RT remains the semantic criterion: substitution must preserve specified denotation.

So DD avoids turning RT into a style label like "pure code only". The requirement is explicit semantic equalities, with effects isolated behind capability boundaries.

One minimal semantic reading is:

#![allow(unused)]
fn main() {
fn f(a: A, io: Io) -> (Result<B, E>, Io)
}

For the same a and same io input, f yields the same (result, io'). This is the DD-style, context-indexed form of substitution reasoning.

Why This Clarifies RT

Many RT debates blur a useful distinction:

  • semantic transparency
  • operational side effects

DD separates these layers:

  • semantic core states meaning
  • interpreters realize execution strategy

This separation allows practical effects without losing law-based reasoning.

Representation-First Failure Mode

If you commit early to concrete representation, RT discussion drifts into style arguments:

  • "this style is pure"
  • "that style is impure"
  • "IO wrappers fix it"

These are mostly encoding debates. DD asks a simpler question: what equalities are valid in your specification?

Practical DD Pattern

Define tiny capability traits and write program behavior as extensions over them. Then provide thin concrete interpreters.

#![allow(unused)]
fn main() {
trait FileSystem {
    type Error;

    fn file_read(&self, name: &str) -> Result<String, Self::Error>;
}

trait GithubPublish {
    type Error;

    fn github_publish(&self, repo: &str, files: Vec<String>) -> Result<(), Self::Error>;
}

trait Concurrent {
    fn map_concurrent<A, B, E, F>(&self, xs: &[A], f: F) -> Result<Vec<B>, E>
    where
        A: Clone,
        F: Fn(&A) -> Result<B, E>;
}

#[ext(name = PublishProjectExt)]
impl<This> This
where
    This: FileSystem<Error = String> + GithubPublish<Error = String> + Concurrent,
{
    fn publish_project(&self, files: &[&str], repo: &str) -> Result<(), String> {
        let files = self.map_concurrent(files, |name| self.file_read(*name))?;
        self.github_publish(repo, files)
    }
}
}

#[ext(...)] is a macro from the extend crate used to reduce boilerplate by generating extension methods from an impl block; it is not a new Rust language feature.

In Scala FP, RT is often presented through values of type F[A] (for example IO[A], or F[Either[E, A]]). Here, RT is expressed one level up: as capability-bounded program meaning. So this Rust method is not an RT value container in the same encoding sense as F[A]; it is RT behavior specified over explicit capabilities, with concrete effects delegated to implementations.

The extension states semantic behavior against capabilities. Concrete implementations decide execution strategy: batching, retries, transport, parallelism, and storage details.

Final Insight

In Denotational Design, RT is a property of semantic specifications. Implementation choices are secondary as long as they preserve the stated denotational laws.

Keep the law. Do not get stuck on the label.