Pull to refresh

Referential Transparency as a mechanism for building Reliable Programs

Level of difficulty Easy
Reading time 4 min
Views 251

Referential transparency, a key concept in functional programming, is often associated with more reliable, easier to test, and safer software. This term refers to a principle in which a function, given the same input, will always produce the same output without producing any side effects.

In the real world of software development, side effects are inevitable. Programs are rarely useful unless they interact with the outside world. This interaction could be reading from or writing to the console, making network requests, querying a database, or modifying a variable.

Yet, despite the necessity of side effects, they introduce risks and complexities. Programs with side effects are harder to test, harder to reason about, and more prone to bugs. They can also make the system as a whole more difficult to understand and maintain, due to hidden dependencies between components.

Enter referential transparency - a concept that means a function, given the same input, will always provide the same output, without creating any side effects. A function that adheres to this principle doesn't read any global state or change any state outside of its scope. The result is code that is more predictable and easier to reason about.

In terms of software safety and reliability, the absence of side effects is not enough. Programs should also be free from external influence - their results should only depend on their arguments. That is, programs should not read data from the console, a file, network, database, or even system variables.

Referentially transparent code offers several interesting properties:

  1. Self-sufficiency: It can be used in any context, needing only the permissible arguments.

  2. Determinism: It always returns the same value for the same arguments. It might return an erroneous result, but at least the result will always be the same for the same argument.

  3. Exception-free: It may throw errors, such as out of memory or stack overflow, but these errors explicitly indicate mistakes in the code. These are not situations that you, as a programmer, or the users of your API should handle.

  4. Safe operation: It doesn't create conditions that cause unexpected failure of other code. For instance, it doesn't modify arguments or any other external data.

  5. Device independence: It won't freeze because some external device (database, filesystem, or network) is unavailable, works too slowly, or has broken down.

Benefits of Safe Programming

From the above description, you can likely infer the advantages of referential transparency:

  1. Easier Analysis: Referentially transparent programs are easier to analyze thanks to their determinism. Specific input arguments will always lead to the same result. In many cases, the correctness of such programs can be proven without exhaustive testing and without fear of them behaving unexpectedly under unusual conditions.

  2. Easier Testing: Since such programs don't have side effects, you won't need mocks that are often required for testing to isolate program components from the outside world.

  3. Modularity: Referentially transparent programs have a more modular organization. They are constructed from functions that only have input and output - no side effects, no exceptions, and context changes that need to be handled, no shared mutable state, and no operations simultaneously altering the same state.

  4. Ease of Composition and Reorganization: Building such a program starts with developing various base functions, and then these functions are combined into higher-level functions. This process is repeated until a single function representing the whole program is obtained. And since all these functions are referentially transparent, they can be used to create other programs without any modifications.

  5. Concurrency: Referentially transparent programs naturally support multi-threading because they don't alter shared state. This doesn't mean that all data must be immutable - only shared data should be. Programmers applying this rule soon realize that immutable data is always safer, even if changes aren't visible externally. One reason is that data not meant to be shared won't accidentally become public after refactoring. Widespread use of immutable data ensures such problems will never arise.

In the remainder of this article, I will present several examples of how using referential transparency helps to write safer programs. These are basic examples to help illustrate the concept of referential transparency, and how it might be applied in a Kotlin codebase to increase reliability.

Example number one:

If we have a function that takes a user's ID and returns their email address from a database, a referentially transparent version of this might look like the following. This function does not directly query a database, but takes a database data as input, which makes it referentially transparent. 

data class User(val id: Long, val email: String)
fun getEmail(userId: Long, userMap: Map<Int, User>): String {
    return userMap[userId]?.email ?: "User not found"
fun convertUserEntityToUserMap(userEntities: Set<UserEntity>) =
userEntities.map(UserEntity::toDto).associateBy { it.id }
class UserEntity {
lateinit var id: Long,
lateinit var email: String
fun UserEntity.toDto() = User(this.id, this.email)
interface UserRepository : JpaRepository<UserEntity, Long>

Example number two:

Consider a program that squares each number in a list. This function will not alter the input list but instead creates a new list with the squared numbers. Here is how you might write a referentially transparent function for this in Kotlin.

fun squareList(numbers: List<Int>): List<Int> {
    return numbers.map { it * it }

Example number three:

Here is a referentially transparent function calculates the area of a rectangle. With the same inputs, this function will always give the same result.

fun area(length: Double, width: Double): Double {
  return length * width

Total votes 1: ↑1 and ↓0 +1
Comments 3
Comments Comments 3