Skip to main content

Overview

Ribs aims to provide useful libraries built on the core principles of functional programming (FP). FP is based on a core idea that we should write programs that only use pure functions that are side-effect free and follow the rules of referential transparency.

Let's work through what each of those properties mean in practice:

Side-Effects

To be brief, side effects are any action taken within a program other than returning a result. Common examples of side effects include:

  • Mutating a variable
    int opCount = 0;

    int performOp(int a, int b) {
    opCount += 1; // Mutating global variable!
    return a + b;
    }
  • Printing to console
    int multiply(int a, int b) {
    print('multipying $a x $b'); // Side effect!
    return a * b;
    }
  • Changing a field of an object
    final class Tracker {
    int count = 0;

    Tracker();
    }

    int doubleHeadAndSum(int a, int b, Tracker tracker) {
    tracker.count += 1; // Modifying a field on the Tracker parameter
    return a + b * a;
    }
  • Throwing an Exception
    bool fireMissile(int passcode) {
    if (passcode == 123) {
    return true;
    } else {
    throw Exception('Missle launch aborted: Invalid passcode!');
    }
    }

Eliminating side-effects from our programs allows us to write pure functions.

Pure Functions

A pure function should take it's parameters, compute a result and return that result. It should do nothing else! If a function has any effect on the overall state of the program, it cannot be considered pure and violates a core principle of FP.

One of the simplest examples of this below:

int pureAdd(int a, int b) => a + b;

This simple function takes it's inputs and the result of the function is entirely dependent on the input. Using pure functions allows us to use local reasoning, in that we only need to understand what is happening in the function itself, rather than needing to know any kind of context surrounding the function call.

Referential Transparency

A simplistic definition of referential transparency (RT) refers to a property where any expression (E) can be safely replaced by the result of evaluating that expression. Safely means that the replacing or substitution does not change the result of the entire program.

Building on our definitions let's look at 2 examples that illustrate when RT is satisfied and when it is violated:

Initial Program
final class Counter {
int count = 0;

Counter add() {
count += 1;
return this;
}
}

final counter = Counter();
final b = counter.add();
final resA = b.count;
final resB = b.count;

final areEqual = resA == resB; // Both values here equal 1

Now let's try substituting every occurance of b with the expression that b evaluated to, namely a.add():

Broken Referential Transparency
final counter = Counter();
// final b = a.add(); // We replace all occurances of b with a.add();
final resA = counter.add().count;
final resB = counter.add().count;

final areEqual = resA == resB; // Oh no! resA == 1 while resB == 2!

After doing the substitution, we can see that RT is broken since the value of areEqual changes. The ultimate reason behind this is that the Counter.add function is not a pure function. It changes the mutable field count and then returns itself, which is clearly a side effect.

Additional Resources