Question Everything

“I have no special talent. I am only passionately curious.”

Albert Einstein

A Golden Rule of this book is to always ask, “Why?” By this I mean that you should question everything I present. Ask yourself, “Why is this FP approach better than what I do in my OOP code?” To help develop this spirit, let’s take a little look at what FP is, and then see what questions we might have about it.

What is FP?

I’ll describe FP more completely in the “What is Functional Programming?” lesson, but for the moment let’s use the following function of FP:

  • FP applications consist of only immutable values and pure functions.
  • Pure function means that (a) a function’s output depends only on its input parameters, and (b) functions have no side effects, such as reading user input or writing output to a screen, disk, web service, etc.

While I’m intentionally keeping that definition short, it’s a way that people commonly describe FP, essentially the FP elevator pitch.

What questions come to mind?

Given that description, what questions come to mind about FP?

Some of my first questions were:

  • How can you possibly write an application without reading input or writing output?
  • Regarding I/O:
    • How do I write database code?
    • How do I write RESTful code?
    • How do I write GUI code?
  • If all variables are immutable, how do I handle changes in my code?
    • For instance, if I’m writing an order-entry system for a pizza store, what do I do when the customer wants to change their pizza crust or toppings in the middle of entering an order?

If you have a little more exposure to FP than I did, you might ask:

  • Why is recursion better? Is it really better? Why can’t I just use var fields inside my functions, as long as I don’t share those vars outside the function scope?
  • Is “Functional I/O” really better than “Traditional I/O”?

A little later you might ask:

  • Are there certain applications where the FP approach is better? Or worse?

Decide for yourself what’s better

Critical thinking is an important part of being a scientist or engineer, and I always encourage you to think that way:

Is the approach I’m looking at better or worse than other options? If so, why?

When doing this I encourage you not to make any snap judgments. Just because you don’t like something initially doesn’t mean that thing is bad or wrong.

“The best idea wins”

With critical thinking you also need to tune out the people who yell the loudest. Just because they’re loud, that doesn’t mean they’re right. Just focus on which ideas are the best.

In my book, A Survival Guide for New Consultants, I share this quote from famed physicist Richard Feynman:

“The best idea wins.”

He wrote this in one of his books, where he shared an experience of how Neils Bohr would seek out a very young Feynman during the building of the first atomic bomb. Bohr felt that the other scientists on the project were “Yes men” who would agree with anything he said, while Feynman was young, curious, and unintimidated. Because Feynman was only interested in learning and in trying to come up with the best solutions, he would tell Bohr exactly what he thought about each idea, and Bohr sought him out as a sounding board.

Feynman meant that you have to be able to have good, honest conversations with people about your ideas, and at the end of the day you have to put your ego aside, and the team should go forward with the best idea, no matter where it came from.

This goes back to my point: Don’t blindly listen to people, especially the people who yell the loudest or those who can profit from selling you an idea. Put your critical thinking hat on, and make your own decisions.

A quick aside: Imperative programming

In the next sections I’ll use the term “Imperative programming,” so I first want to give you a definition of what it means.

With a few minor changes, Wikipedia offers this description: “Imperative programming is a programming paradigm that uses statements that change a program’s state. It consists of a series of commands for the computer to perform. It focuses on describing the details of how a program operates.”

This Quora page adds: “Imperative programming involves writing your program as a series of instructions (statements) that actively modify memory (variables, arrays). It focuses on ‘how,’ in the sense that you express the logic of your program based on how the computer would execute it.”

If you’ve ever disassembled a JVM “.class” file with javap -c to see code like this:

public void main(java.lang.String[]);
  Code:
     0: aload_0
     1: aload_1
     2: invokestatic  #60
     5: return

That’s the extreme of what they’re referring to: imperative programming at a very low level. This code tells the JVM exactly how it needs to solve the problem at hand.

A critical thinking exercise

To start putting your critical thinking skill to work, I’m going to show you two versions of the same algorithm. As you see the two algorithms, I want you to jot down any questions you have about the two.

First, here’s an imperative version of a sum method:

def sum(ints: List[Int]): Int = {
    var sum = 0
    for (i <- ints) {
        sum += i
    }
    sum
}

This code modifies a var field within a for loop — a very common pattern in imperative programming.

Next, here’s a Scala/FP version of that same method:

def sum(xs: List[Int]): Int = xs match {
    case Nil => 0
    case x :: tail => x + sum(tail)
}

Notice that this method uses a match expression, has no var fields, and it makes a recursive call to sum in the last line of the method body.

Given those two versions of the same algorithm, what questions come to your mind?

My questions

The questions you have will depend heavily on your experience. If you’re very new to Scala/FP your first question might be, “How does that second method even work?” (Don’t worry, I’ll explain it more in the lessons on writing recursive functions.)

I remember that some of my first questions were:

  • What’s wrong with the imperative approach? Who cares if I use a var field in a for loop inside a function? How does that affect anything else?
  • Will the recursive function blow the stack with large lists?
  • Is one approach faster or slower than the other?
  • Thinking in the long term, is one approach more maintainable than the other?
  • What if I want to write a “parallel” version of a sum algorithm (to take advantage of multiple cores); is one approach better than the other?

That’s the sort of thinking I want you to have when you’re reading this book: Question everything. If you think something is better, be honest, why do you think it’s better? If you think it’s worse, why is it worse?

In the pragmatic world I live in, if you can’t convince yourself that a feature is better than what you already know, the solution is simple: Don’t use it.

As I learned FP, some of it was so different from what I was used to, I found that questioning everything was the only way I could come to accept it.

“We write what we want, not how to do it”

As another example of having a questioning attitude, early in my FP learning process I read quotes from experienced FP developers like this:

“In FP we don’t tell the computer how to do things, we just tell it what we want.”

When I read this my first thought was pretty close to, “What does that mean? You talk to the computer?”

I couldn’t figure out what they meant, so I kept questioning that statement. Were they being serious, or was this just some sort of FP koan, trying to get you interested in the topic with a mysterious statement? It felt like they were trying to sell me something, but I was open to trying to understand their point.

After digging into the subject, I finally decided that the main thing they were referring to is that they don’t write imperative code with for loops. That is, they don’t write code like this:

def double(ints: List[Int]): List[Int] = {
    val buffer = new ListBuffer[Int]()
    for (i <- ints) {
        buffer += i * 2
    }
    buffer.toList
    foo
    bar
}

val newNumbers = double(oldNumbers)

Instead, they they write code like this:

val newNumbers = oldNumbers.map(_ * 2)

With a for loop you tell the compiler the exact steps you want it to follow to create the new list, but with FP you say, “I don’t care how map is implemented, I trust that it’s implemented well, and what I want is a new list with the doubled value of every element in the original list.”

In this example, questioning the “We write what we want” statement is a relatively minor point, but (a) I want to encourage a curious, questioning attitude, and (b) I know that you’ll eventually see that statement somewhere, and I wanted to explain what it means.

What’s next?

In the next lesson I’m going to provide a few programming rules that I’ll follow in this book. While I’m generally not much of a “rules” person, I’ve found that in this case, having a few simple rules makes it easier to learning functional programming in Scala.

 

books i’ve written