Benefits of Referential Transparency in Functional Programming

functional programming

In programming, what are the benefits of referential transparency?

RT makes one of the major differences between functional and imperative paradigms, and is often used by advocates of the functional paradigm as a clear advantage over the imperative one; but in all of their efforts, these advocates never explain why it is a benefit to me as a programmer.

Sure, they'll have their academic explanations to how "pure" and "elegant" it is, but how does it make it better than a less "pure" code? How does it benefit me in my day-to-day programming?

Note: This is not a duplicate of What is referential transparency? The latter addresses the topic of what is RT, while this question adressses its benefits (which may not be so intuitive).

Best Answer

The benefit is that pure functions make your code easier to reason about. Or, in another words, side effects increase the complexity of your code.

Take an example of computeProductPrice method.

A pure method would ask you for a product quantity, a currency, etc. You know that whenever the method is called with the same arguments, it will always produce the same result.

  • You can even cache it and use the cached version.
  • You can make it lazy and postpone its call to when you actually need it , knowing that the value won't change meanwhile.
  • You can call the method multiple times, knowing that it won't have side effects.
  • You can reason about the method itself in an isolation from the world, knowing that all it needs are the arguments.

A non-pure method will be more complex to use and debug. Since it depends on the state of the variables other than the arguments and possibly altering them, it means that it could produce different results when called multiple times, or not have the same behavior when not called at all or called too soon or too late.

Example

Imagine there is a method in the framework which parses a number:

decimal math.parse(string t)

It doesn't have referential transparency, because it depends on:

  • The environment variable which specifies the numbering system, that is Base 10 or something else.

  • The variable within the math library which specifies the precision of numbers to parse. So with the value of 1, parsing the string "12.3456" will give 12.3.

  • The culture, which defines the expected formatting. For instance, with fr-FR, parsing "12.345" will give 12345, because the separation character should be ,, not .

Imagine how easy or difficult would it be to work with such method. With the same input, you can have radically different results depending on the moment when you call the method, because something, somewhere changed the environment variable or switched the culture or set a different precision. The non-deterministic character of the method would lead to more bugs and more debugging nightmare. Calling math.parse("12345") and obtaining 5349 as an answer since some parallel code was parsing octal numbers isn't nice.

How to fix this obviously broken method? By introducing referential transparency. In other words, by getting rid of global state, and moving everything to the parameters of the method:

decimal math.parse(string t, base=10, precision=20, culture=cultures.en_us)

Now that the method is pure, you know that no matter when you call the method, it will always produce the same result for the same arguments.

Related Topic