Referential Transparency

Referential Transparency is a fancy term to describe that a pure function can safely be replaced by its expression. An example will help illustrate this.

In Algebra when you had the following formula:

y = x + 10

And were told:

x = 3

You could substituted x back into the equation to get:

y = 3 + 10

Notice that the equation is still valid. We can do the same kind of substitution with pure functions.

Here’s a function in Elm that puts single quotes around the supplied string:

quote str = "'" ++ str ++ "'"

And here’s some code that uses it:

findError key = "Unable to find " ++ (quote key)

Here findError builds an error message when a search for key is unsuccessful.

Since the quote function is pure, we can simply replace the function call in findError with the body of the quote function (which is just an expression):

findError key = "Unable to find " ++ ("'" ++ str ++ "'")

This is what I call Reverse Refactoring (which makes more sense to me), a process that can be used by programmers or programs (e.g. compilers and test programs) to reason about code.

This can be especially helpful when reasoning about recursive functions.

Execution Order

Most programs are single-threaded, i.e. one and only one piece of code is being executed at a time. Even if you have a multithreaded program, most of the threads are blocked waiting for I/O to complete, e.g. file, network, etc.

This is one reason why we naturally think in terms of ordered steps when we write code:

1. Get out the bread2. Put 2 slices into the toaster3. Select darkness4. Push down the lever5. Wait for toast to pop up6. Remove toast7. Get out the butter8. Get a butter knife9. Butter toast

In this example, there are two independent operations: getting butter and toasting bread. They only become interdependent at step 9.

We could do steps 7 and 8 concurrently with steps 1 through 6 since they are independent from one another.

But the minute we do this, things get complicated:

Thread 1--------1. Get out the bread2. Put 2 slices into the toaster3. Select darkness4. Push down the lever5. Wait for toast to pop up6. Remove toast

Thread 2--------1. Get out the butter2. Get a butter knife3. Wait for Thread 1 to complete4. Butter toast

What happens to Thread 2 if Thread 1 fails? What is the mechanism to coordinate both threads? Who owns the toast: Thread 1, Thread 2 or both?

It’s easier to not think about these complexities and leave our program single threaded.

But when it’s worth squeezing out every possible efficiency of our program, then we must take on the monumental effort to write multithreading software.

However, there are 2 main problems with multithreading. First, multithreaded programs are difficult to write, read, reason about, test and debug.

Second, some languages, e.g. Javascript, don’t support multithreading and those that do, support it badly.

But what if order didn’t matter and everything was executed in parallel?

While this sounds crazy, it’s not as chaotic as it sounds. Let’s look at some Elm code to illustrate this:

Here buildMessage takes message and value then produces an uppercased message, a colon and value in single quotes.

Notice how upperMessage and quotedValue are independent. How do we know this?

There are 2 things that must be true for independence. First, they must be pure functions. This is important because they must not be affected by the execution of the other.

If they were not pure, then we could never know that they’re independent. In that case, we’d have to rely on the order that they were called in the program to determine their execution order. This is how all Imperative Languages work.

The second thing that must be true for independence is that the output of one function is not used as the input of the other. If this was the case, then we’d have to wait for one to finish before starting the second.

In this case, upperMessage and quotedValue are both pure and neither requires the output of the other.

Therefore, these 2 functions can be executed in ANY ORDER.

The compiler can make this determination without any help from the programmer. This is only possible in a Pure Functional Language because it’s very difficult, if not impossible, to determine the ramifications of side-effects.

The order of execution in a Pure Functional Language can be determined by the compiler.

This is extremely advantageous considering that CPUs are not getting faster. Instead, manufactures are adding more and more cores. This means that code can execute in parallel at the hardware level.

Unfortunately, with Imperative Languages, we cannot take full advantage of these cores except at a very coarse level. But to do so requires drastically changing the architecture of our programs.

With Pure Functional Languages, we have the potential to take advantage of the CPU cores at a fine grained level automatically without changing a single line of code.

I’ve bolded the types which makes them stand out but they still interfere with the function definition. You have to read it carefully to find the names of the variables.

With Dynamically Typed Languages, this is not a problem. In Javascript, we can write code like:

var getPerson = function(people, personId) { // ...};

This is so much easier to read without all of that nasty type information getting in the way. The only problem is that we give up the safety of typing. We could easily pass in these parameters backwards, i.e. a Number for people and an Object for personId.

We wouldn’t find out until the program executed, which could be months after we put it into production. This would not be the case in Java since it wouldn’t compile.

But what if we could have the best of both worlds. The syntactical simplicity of Javascript with the safety of Java.

It turns out that we can. Here’s a function in Elm with Type Annotations:

add : Int -> Int -> Intadd x y = x + y

Notice how the type information is on a separate line. This separation makes a world of difference.

Now you may think that the type annotation has a typo. I know I did when I first saw it. I thought that the first -> should be a comma. But there’s no typo.

When you see it with the implied parentheses it makes a bit more sense:

add : Int -> (Int -> Int)

This says that add is a function that takes a single parameter of type Int and returns a function that takes a single parameter Int and returns an Int.

This says that doSomething is a function that takes a single parameter of type String and returns a function that takes a single parameter of type Int and returns a function that takes a single parameter of type String and returns a String.

Notice how everything takes a single parameter. That’s because every function is curried in Elm.

Since parentheses are always implied to the right, they are not necessary. So we can simply write:

doSomething : String -> Int -> String -> String

Parentheses are necessary when we pass functions as parameters. Without them, the type annotation would be ambiguous. For example:

My Brain!!!!

If you liked this, click the💚 below so other people will see this here on Medium.

If you want to join a community of web developers learning and helping each other to develop web apps using Functional Programming in Elm please check out my Facebook Group, Learn Elm Programming https://www.facebook.com/groups/learnelm/