Return Values Are Not a Bug
The “return self from every method” style of API design has a real appeal: it enables fluent chains like obj.foo.bar.baz. But treating self-return as a default convention carries a hidden cost. A method that returns the receiver is very often a method that mutates the receiver. And mutation, while sometimes necessary, is exactly the kind of behavior you want to isolate rather than encourage.
Why Mutation Deserves Suspicion
Mutable state makes programs harder to reason about at every level. A mutable object used as a hash key can break lookups after a field changes. Passing a mutable object into a function means you cannot trust its value afterward without carefully auditing that function's behavior. Concurrency with shared mutable state is where correctness goes to die.
None of this is an argument for pure functional programming as a universal dogma. Programs must change state to do useful work—I/O alone guarantees that. Mutation is often faster and can reduce garbage-collection pressure. The point is that these benefits come with a tax on clarity. When more of your code operates on stable values, comparisons stay valid, arguments stay intact, and thread-safety reasoning gets easier.
You do not need Haskell's type system to benefit. Even in a language like Ruby, a culture that makes mutation explicit—gsub vs. gsub! is the canonical example—reduces the chance of bugs without eliminating them categorically.
What Certain Return Types Signal
When you see a method whose return type is void, or that always returns nil, or that returns self—read that as a warning. Such a signature strongly suggests the function is doing something other than computing a value. It might mutate the receiver. It might mutate its arguments. It might mutate variables in lexical scope, write to the filesystem, send a network packet, or set a global.
The intuition is simple: there is exactly one meaningful pure function that returns nothing, one that returns nil, and a small family of identity functions that return their first argument. When you see those signatures repeatedly, you are probably looking at a program where mutable state is doing heavy lifting.
The Mathematics of Constant Functions
This intuition can be made rigorous. For any value r, there is exactly one pure function that always returns r—assuming you model functions properly, including their effects on an environment.
To see why, model a function as a set of tuples (e, e', x, y), where e is the initial environment, e' the final environment, x the input, and y the return value. Two constraints apply: the function must be complete (defined for all inputs and environments) and deterministic (input and environment uniquely determine output and final environment). A function is pure if the environment is unchanged—that is, e = e' for all tuples.
Now suppose there are two distinct pure functions, f and g, both of which always return r over some domain X and environment set E. Since both are pure and always return r, they are simply different names for the same set:
f = {(e, e, x, r) | e ∈ E, x ∈ X}g = {(e, e, x, r) | e ∈ E, x ∈ X}
These are identical sets. The contradiction means f and g cannot be distinct. The same argument applies to functions that return their first argument: for each arity, there is exactly one pure identity-style function.
This result extends across domains. Given two pure constant functions f over X1 and g over X2, their union h = f ∪ g is also a pure function—it does not mutate its environment, it is complete over X1 ∪ X2, and it deterministically returns r. So there is no good reason to define more than one pure void, nil, or constant-returning function in a program, except perhaps for static type safety.
In practice, this means a single Clojure identity function can stand in for any pure function that returns its first argument, regardless of type:
user=> (def selfie (fn [self & args] self)))
#'user/selfie
user=> (selfie 3)
3
user=> (selfie "channing" "tatum")
"channing"
Returning Self Is Just Identity in Disguise
Consider two Ruby methods that both return self:
def meow
self
end
def stretch
nil
ENV["USER"] + " in spaaace"
5.3 / 3
self
end
meow is nothing more than the identity function. By the proof above, so is stretch—the only difference is dead code that a compiler or careful reader would eliminate. If you call another method and then return self, you have two possibilities. If the intermediate call is pure, the whole method is pure and collapses to plain identity. If the intermediate call is impure, then your method is also impure, and you have introduced mutation into the picture.
The Practical Takeaway
When you encounter a function that returns void, nil, or self, the useful question is not “how do I chain this?” but “what is this mutating?”
If you have a genuinely pure function—say, one that counts explosions in a film—and you force it to return self to fit a fluent style, you are converting a clean computation into an impure one. You have to add mutable state to achieve that return type. The better direction is the opposite: trim mutation wherever you can and let functions return meaningful values.



