Referential transparency is the property that an expression may be replaced by the value it evaluates to without changing a program’s meaning. A pure function has this property for every referentially transparent input: it computes a result determined only by its input and has no observable effect besides returning that result. Addition and the length of an immutable string fit this model. section 1.2
The practical test is substitution. If an expression stands for a value, replace each occurrence with that value and ask whether the surrounding program behaves the same. When replacement is safe, computation can be understood like algebra: expand terms, substitute equals for equals, and reduce the expression. This supports local reasoning, since a transparent expression keeps its meaning wherever it appears. section 1.3 section 4.1
A hidden charge fails substitution
A coffee-purchase function may return only a Coffee while also charging a credit card. Replacing buyCoffee(aliceCreditCard) with Coffee() preserves the returned cup but removes the authorization request. The two expressions are observably different, so the function is not referentially transparent. The invariant is demanding but useful: everything a function does must be represented by its returned value and result type. section 1.3
scala> val x = new StringBuilder("Hello")
x: java.lang.StringBuilder = Hello
scala> val r1 = x.append(", World").toString
r1: java.lang.String = Hello, World
scala> val r2 = x.append(", World").toString
r2: java.lang.String = Hello, World, World #1
Repeated mutation makes an apparently similar expression yield different results: the first append produces “Hello, World”; the second observes the already-modified builder and produces “Hello, World, World”. Replacing either call with its earlier result would change behavior. section 1.3
Not every use of mutation makes a whole function impure. Mutation confined to data created inside a function can remain unobservable to callers. An in-place quicksort may mutate a local array, then return a list; because no outside code can retain the array reference, quicksort(xs) can still be substituted by its resulting list for transparent xs. Purity concerns the externally observable expression, not a ban on every operational technique. section 14.1
When substitution fails, make the relevant effect explicit rather than hiding it:
- Return a generated value together with the next state. A random-number generator can return (Int, RNG), leaving the old generator unchanged and giving the caller control over subsequent state. section 6.2
- Return failure as an ordinary value such as Option, Either, Try, or Validated instead of throwing. This keeps error possibilities available to types and composable operations. section 4.0 section 4.6
- Represent I/O requests as a pure program and choose an interpreter separately. The interpreter may use real effects or a pure State or Reader interpretation for testing. section 13.4.3 section 13.10
Transparency is contextual. Constructing two equal-looking objects can create distinct references, which most code cannot observe but reference equality can. Thus an expression is referentially transparent with respect to a program when replacing its occurrences does not affect that program’s meaning. The question is not merely whether an implementation performs an action, but whether the surrounding context can observe a difference. section 14.3








