FP: why bother?

Jake Woods

What are we going to learn today?

Nothing!

I'm not going to teach you anything.

No sales pitches!

Instead! We're going to explore

How do functional programmers think about problems?

Why do functional programmers bother with all this arcana?

Caveat

My experience is with statically typed FP

I.e. Haskell and Scala

Dynamically typed FP is perfectly valid!

I.e. Clojure and… Ruby?

I just don't do much of it so my examples are going to be biased

Sorry!

Learning FP

Purity

def add(a: Int, b: Int): Int = {
  a + b
}
add(1, 1) // It returns 2! Purely!!

Recursion

def fib(n: Int): Int = {
  if(n == 0) {
    0
  } else {
    n + fib(n - 1)
  }
}
fib(4) // returns 10!

Currying

val add = (a: Int) => {
  (b: Int) => {
    a + b
  }
}
add(1)(2) // Returns 3! Cool!

List processing

// List processing:
val numbers = [1, 1, 1]

numbers.map(i => i + 1).sum // Returns 6! Cool!

Back to reality

// Making decisions & causing side effects:
def makeLotsOfMoney(database: DbConnection): Unit = {
  val monies: Array[Float] = database.getMoney()

  val isThereEnoughMoney = true
  monies.foreach { money =>
    if(money < 1000000) {
      isThereEnoughMoney = false
    }
  }

  if(!isThereEnoughMoney && someBusinessThing() && !globalVariable) {
    doSomethingWithAWS()
  } else {
    globalVariable = true

    log.error(
      "Something bad happened but it's been happening for years so don't worry!"
    )

    doSomethingElseWithAWS()
  }
}

They look completely different!

How does this arcana relate to me?

Why bother!?

Why bother with anything?

Programming

  • Express problems to a computer
  • … to make it do stuff
  • … and maybe have some fun!

Algorithms

  • Express problems with math
  • … to make it do new things it couldn't do before
  • … to make it easier to solve problems

Design patterns

  • Express problems with unthinking cargo cult code copying
  • … to suck the fun out of everything
  • … to make it easier to solve problems??
  • … I'm looking at you ProxyAbstractFactorySingletonBeanManagerDecorator!

Imperative programming

  • Express problems in terms of imperative statements
  • … to make computers do stuff
  • … to make it easier to solve problems

Object Oriented Programming

  • Express problems in terms of objects
  • … to make computers do stuff
  • … to make it easier to solve problems

Functional Programming

  • Express problems in terms of functions
  • … to make computers do stuff
  • … to make it easier to solve problems

It's not a competition

Functional programming is not morally better

We just think it's a great way to:

  • Solve problems
  • Have fun
  • Express ourselves

Why bother with FP?

Short answer: Because we think pure functions are gods gift to the mortal world.

A totally legitimate description of pure functions

  • Behavior only depends on parameters
  • Output only by returning a value
  • No user-detectable changes to the computer
    • No logs, no drawing things, no network access, nothing!

Impure example (Scala)

// Games in the datbase: ["Dwarf Fortress", "Duke Nukem Forever", "Prey", "E.T."]
var gameDatabase = gameServer.getGameDatabase

// Impure
def filterBadGames(): Unit = {
  gameDatabase.remove("Duke Nukem Forever")
  gameDatabase.remove("E.T.")
}

filterBadGames()
println(gameDatabase) // ["Dwarf Fortress", "Prey"]!

Impure example (Haskell)

I started writing this example and it was so ugly I gave up.

Impure example (JavaScript)

// Games in the datbase: ["Dwarf Fortress", "Duke Nukem Forever", "Prey", "E.T."]
var gameDatabase = gameServer.getGameDatabase

// Impure
function filterBadGames() {
  gameDatabase.remove("Duke Nukem Forever");
  gameDatabase.remove("E.T.");
}

filterBadGames()
console.log(gameDatabase) // ["Dwarf Fortress", "Prey"]!

Pure Example (Scala)

// Games in the datbase: ["Dwarf Fortress", "Duke Nukem Forever", "Prey", "E.T."]
val gameDatabase = gameServer.getGameDatabase

// Pure
def filterBadGames(games: Array[String]): Array[String] = {
  games
    .filter(_ ==  "Duke Nukem Forever")
    .filter(_ == "E.T.")
}

val goodGames = filterBadGames(gameDatabase)
println(goodGames) // ["Dwarf Fortress", "Prey"]

Pure Example (Haskell)

-- Games in the datbase: ["Dwarf Fortress", "Duke Nukem Forever", "Prey", "E.T."]
gameDatabase :: IO [String]
gameDatabase = getGameDatabase

-- Pure
filterBadGames :: [String] -> [String]
filterBadGames = (delete "Duke Nukem Forever") . (delete "E.T.")

main :: IO ()
main = do
  games <- gameDatabase
  let goodGames = filterBadGames games
  putStrLn goodGames  -- ["Dwarf Fortress", "prey"]

Pure Example (JavaScript)

// Games in the datbase: ["Dwarf Fortress", "Duke Nukem Forever", "Prey", "E.T."]
const gameDatabase = gameServer.getGameDatabase

// Pure
function filterBadGames(games) {
  games.filter(_ ==  "Duke Nukem Forever")
    .filter(_ == "E.T.");
}

const goodGames = filterBadGames(gameDatabase)
println(goodGames) // ["Dwarf Fortress", "Prey"]

Benefits of purify

Super easy to test

"filterBadGames" should {
  "keep Prey" in {
    filterBadGames(["Prey"]) must beEqualTo(["Prey"])
  }

  "filter out Duke Nukem Forever" in {
    filterBadGames(["Duke Nukem Forever"]) must beEmpty
  }
}

Bam!

More benefits of purity

Everything you need to know is in one place.

def filterBadGames(games: Array[String]): Array[String] = {
  games
    .filter(_ ==  "Duke Nukem Forever")
    .filter(_ == "E.T.")
}

I know for a fact that filterBadGames only uses an array of strings. Nothing else!

I also know it's not going to do something unexpected!

Even more benefits of purity

The static typing is actually telling the truth!

def filterBadGames(games: Array[String]): Array[String]

This function is making a promise: Give me some strings and I will give you back some strings. Nothing more, nothing less

Pure functions are good friends: They keep their promises

Drawbacks

Pure functions can't do anything!

Things of note if you accept pure functions as your lord and saviour

  • Immutability becomes an obvious choice
  • Objects start to look a lot less useful
  • Since you've got lots of pure functions you need new techniques to manage them

Lock the doors, it's Monad time

Just kidding

How many of you had a fear response just then?

The functional arcana is scary

It looks impenetrable

So why do we do it?

Each scary buzzword is a solution to a problem

A solution that leverages pure functions

Immutability

Makes it possible to write pure functions

Functors

Makes it easier to apply functions to different types of containers

def add5(input: Int): Int = input + 5
// add5 works on ints!
add5(10) // Returns 15
// Functor lets us use it on lists!
List(1,2,3).map(add5) // returns List(6,7,8)
// And Options
Some(20).map(add5) // Returns List(25)
// And Future!
Future(1).map(add5) // Returns Future(6)

And lots of other stuff!

Applicatives

Combine functions in parallel

Monads

Sequence functions together

This is used for IO in FP as IO is sequential!

But Monads are not the core of FP and you can do FP without monads.

Fold

Makes it easier to apply functions to recursive data

Traverse

Makes it easier to apply functions to nested data structures

Typeclasses

Make it easier to pick which function to apply to some data

Free/Eff

  • Compile functions into more functions
  • Compose monads

They're all solutions to real world problems

But Jake - Why do you bother!?

I wanted to make Video Games!

  • So I learned Visual Basic 6
  • And used it to build crappy games

Then I learned about OOP!

  • Lots of C++
  • Lots of objects
  • Everything was great!

And then it happened

  • I had a spaceship game with asteroids, bullets and rocks
  • And I asked the question:
  • "Where should the collide method go?"

Boom!

  • None of the answers that I had at the time were satisfactory.
  • Double dispatch!? That's insane!
  • Let's change everything. That'll solve it!

Then I learned about Haskell

  • I install ghc!
  • And I completely fail to become productive
  • But I learn about the concept of purity and I love it!

A year passes

  • "Maybe I should give Haskell another go!"
  • I install ghc!
  • And I learn a little bit more, apparently Monads are what the cool kids use
  • I fail to become productive again
  • Repeat for several years

Scala? I think?

  • At some point I learn about Scala
  • I try to do FP in it
  • I fail
  • I go back to Haskell again
  • I fail

I apply at REA

  • My toy robot is the first Scala code I've ever written. It's fully functional though!
  • I get in somehow
  • Suckers
  • All that failing turns into a modest amount of knowledge!
  • Things happen
  • Now I'm giving this talk

That's my story!

If you're just getting started

  • You don't need to start with monads/functors/applicatives.
  • Just look for opportunities to write more pure functions
  • See how they make you feel! :)

Thank you!

Questions?