Practical
Functional Programming

 

An intro

 
 
 
 
 
Fjodor Ekström, Hugo Josefson / 2016

Overview

 
  • Why are we doing this?
  • Immutable data
     
  • map
  • filter
  • ...
     
  • Exercises
     
  • References, Questions!

Why are we doing this?

Correct

Comprehensible

Composable

We want our programs to be

Correct programs

  • Do what we mean
  • Do what we expect
  • ...always.

Comprehensible programs

  • We can easily comprehend,
    what will happen when they run
     
  • We can easily comprehend,
    what the program means

Composable programs

  • Are made up of smaller parts
     
  • Each part is Correct, Comprehensible, Composable
     
  • Parts are swappable,
    click into each other

Immutable data

Immutable, wat?

Mutate

  • Make change in-place
     
  • ...to data
  • ...to data structure (tree, array)

Mutable data

  • Data or data structure
    that can be mutated
    (changed in-place)
     
  • Example:
    • int variable
    • array
    • object with set-methods

      (in many languages)

Mutate

let a = 1      // a == 1
let a = a + 1  // a == 2
let obj = {x: 5, y: 20}  // obj == {x:  5, y: 20}
obj.x = 10               // obj == {x: 10, y: 20}

Mutate

class Person {
    int age = 0

    function haveBirthday() {
        this.age++
    }
}

Person newBorn = new Person()
newBorn.age  // 0

newBorn.haveBirthday()
newBorn.age  // 1

Mutate

string[] words = ["Good", "morning", "all"]

function changeTime(string newTime) {
    words[1] = newTime
}

function addWord(string word) {
    words[words.length + 1] = word
}

// ---
changeTime("afternoon") // words == ["Good", "afternoon", "all"]
addWord("the")          // words == ["Good", "afternoon", "all", "the"]
addWord("time")         // words == ["Good", "afternoon", "all", "the", "time"]

Immutable data

Immutable data

  • Data or data structure
    that cannot be mutated
     
  • Example: String
    (in many languages)
     
  • Change:
    • copy with some
      parts replaced

Immutable data

string hello = "HELLO WORLD!"
string hellow = hello.toLower()

hello   //"HELLO WORLD!"
hellow  //"hello world!"

hello === hellow //false
string hello = "HELLO WORLD!"
string hellow = hello.toLower()

hello   //"HELLO WORLD!"
hellow  //"hello world!"

Immutable data structure

Immutable data

class Person {
    int age = 0

    function haveBirthday() {
        cloneOfMe = this.cloneAndSet({ age: this.age+1 });
        return cloneOfMe
    }
}

Person newBorn = new Person()
Person toddler = newBorn.haveBirthday()

newBorn.age  // 0
toddler.age  // 1

Immutable data

string[] words = ["Good", "morning", "all"]

function changeTime(string[] ws, string newTime) {
    clonedWords = ws.clone()
    clonedWords[1] = newTime
    return clonedWords
}

function addWord(string[] ws, string word) {
    clonedWords = ws.clone()
    clonedWords[clonedWords.length + 1] = word
    return clonedWords
}

// ---
changeTime(words, "afternoon") // returns  ["Good", "afternoon", "all"]
                               // words == ["Good", "morning", "all"]

addWord(addWord(words, "the"), "time") 
                               // returns  ["Good", "morning", "all", "the", "time"]
                               // words == ["Good", "morning", "all"]
string[] words = ["Good", "morning", "all"]

function changeTime(string newTime) {
    words[1] = newTime
}

function addWord(string word) {
    words[words.length + 1] = word
}

// ---
changeTime("afternoon") // words == ["Good", "afternoon", "all"]
addWord("the")          // words == ["Good", "afternoon", "all", "the"]
addWord("time")         // words == ["Good", "afternoon", "all", "the", "time"]

Immutable data benefits

  • Don't worry about your data being mutated
    by code you send it to [Correct]
     
  • When reading the code,
    less (possible state mutation) to
    keep track of [Comprehensible]
     
  • Multithreading locks not needed [Correct]
     
  • Easy to keep previous versions of state
     
  • Optimized comparison of data structures
    (compare references instead of values inside)

map

the concept!

map

}

input container

mapper

output container

const numbers = [1, 2, 3, 4]
const doubledNumbers = []

for (let i = 0; i < numbers.length; i++) {   // imperative
    doubledNumbers[i] = numbers[i] * 2       // style
}

doubledNumbers //[2, 4, 6, 8]
const numbers = [1, 2, 3, 4]
const doubledNumbers = numbers.map(number => number * 2)



doubledNumbers //[2, 4, 6, 8]

vs

const numbers = [1, 2, 3, 4]
const doubledNumbers = numbers.map(function(number) {
    return number * 2
})

doubledNumbers //[2, 4, 6, 8]
const numbers = [1, 2, 3, 4]
const doubledAndIncrementedNumbers = []

for (let i = 0; i < numbers.length; i++) {
    doubledAndIncrementedNumbers[i] = numbers[i] * 2 + 1
}

doubledAndIncrementedNumbers //[3, 5, 7, 9]
const numbers = [1, 2, 3, 4]

const doubledAndIncrementedNumbers = numbers
    .map(number => number * 2)
    .map(number => number + 1)

doubledAndIncrementedNumbers //[3, 5, 7, 9]

vs

const numbers = [1, 2, 3, 4]

const doubledAndIncrementedNumbers = numbers
    .map(number => number * 2)


doubledAndIncrementedNumbers //[2, 4, 6, 8]

Syntax Examples

map

map in Java

String[] input = new String[]{"alice", "bob", "paul", "ellie"};

String[] output = Arrays.stream(input)
                        .map(s -> s.toUpperCase())
                        .toArray(String[]::new);

output // {"ALICE", "BOB", "PAUL", "ELLIE"}

map in Swift

let values = [2.0, 4.0, 5.0, 7.0]

let squares = values.map {$0 * $0}

squares // [4.0, 16.0, 25.0, 49.0]

map in JavaScript

const values = [2, 4, 5, 7]

const squares = values.map(n => n * n)     // ES2015

squares // [4, 16, 25, 49]
var values = [2, 4, 5, 7]

var squares = values.map(function (n) {    // ES5
    return n * n
})

squares // [4, 16, 25, 49]

map in Ruby

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map{|e| e*3 }

# returns [3, 6, 9, 12, 15, 18, 21, 24, 27, 30]

map in C#

List<string> words = new List<string>() {
    "an", "apple", "a", "day"
};

var query = from word in words
            select word.Substring(0, 1);

foreach (string s in query)
    Console.WriteLine(s);

/* This code produces the following output:

    a
    a
    a
    d
*/

map in F#

let helloGood = 
    let list = ["a";"b";"c"]
    list |> List.map (fun element -> "hello " + element)

// val helloGood : string list = ["hello a"; "hello b"; "hello c"]

map in Scala

val numbers = List(1, 2, 3, 4)
numbers.map((i: Int) => i * 2)

// List(2, 4, 6, 8)

map in Python

items = [1, 2, 3, 4, 5]
squared = list( map(lambda x: x**2, items) )

// [1, 4, 9, 16, 25]

map in Elm

map sqrt [1, 4, 9] == [1, 2, 3]

map not [True, False, True] == [False, True, False]

plusOne = (+) 1
map plusOne [1, 4, 9] == [2, 5, 10]

filter

the concept!

filter

}

input container

must be at least this big:

output container

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9]
numbers.filter(isGood)       //[6, 8, 10]
isMoreThanFive = n => n > 5
isEven         = n => n%2 == 0
numbers
    .filter(isMoreThanFive)  //[6, 7, 8, 9, 10]
    .filter(n => isEven(n))  //[6,    8,    10]

isGood = n => isMoreThanFive(n) && isEven(n)

Syntax Examples

filter

filter in Java

String[] input = new String[]{"alice", "bob", "paul", "ellie"};

String[] output = Arrays.stream(input)
                        .filter(s -> s.length() > 4)
                        .toArray(String[]::new);

output // {"alice", "ellie"}

filter in Swift

let digits = [1, 4, 10, 15]
let even = digits.filter { $0 % 2 == 0 }
// [4, 10]

filter in JavaScript

const values = [1, 4, 10, 15]

const even = values.filter(n => n%2 === 0) // ES2015

even // [4, 10]
var values = [1, 4, 10, 15]

var even = values.filter(function (n) {    // ES5
    return n%2 === 0
})

even // [4, 10]

filter in Ruby

[1,2,3,4,5,6,7,8,9,10].select{|el| el%2 == 0 }
# returns [2,4,6,8,10]

filter in C#

string[] words = { "the", "quick", "brown", "fox", "jumps" };

IEnumerable<string> query = from word in words
                            where word.Length == 3
                            select word;

foreach (string str in query)
    Console.WriteLine(str);

/* This code produces the following output:

    the
    fox
*/

filter in F#

[1..10] |> List.filter (fun i -> i%2 = 0) // even

// [2; 4; 6; 8; 10]

filter in Scala

val numbers = List(1, 2, 3, 4)
numbers.filter((i: Int) => i % 2 == 0)

// List(2, 4, 6, 8)

filter in Python

number_list = range(-5, 5)
less_than_zero = list( filter(lambda x: x < 0, number_list) )
print(less_than_zero)

# Output: [-5, -4, -3, -2, -1]

filter in Elm

filter isEven [1..6] == [2,4,6]

reduce

the concept!

BONUS

reduce

}

input container

+

output

start: 0

3

2

3

8

reduce

}

input container

return the largest of two planets

output

start: .

const numbers = [3, 3, 2]
const sum = numbers.reduce(
    (sumSoFar, number) => sumSoFar+number,  // <-- reducer fn
    0                                       // <-- init value
)

sum // 8
first call to the reducer fn:    sumSoFar = 0; number = 3; sumSoFar+number = 3 
second call to the reducer fn:   sumSoFar = 3; number = 3; sumSoFar+number = 6
third call to the reducer fn:    sumSoFar = 6; number = 2; sumSoFar+number = 8
// numbers.reduce(...) returns 8
const numbers = [3, 3, 2]
const sum = numbers.reduce(
    (sumSoFar, number) => sumSoFar+number,  // <-- reducer fn
    0                                       // <-- init value
)

sum // 8
const plus = (a, b) => a + b

const numbers = [3, 3, 2]
const sum = numbers.reduce(plus, 0)

sum // 8

Syntax Examples

reduce

reduce in Java

String[] input = { "this", "is", "a", "sentence" };

String output = Arrays.stream(myArray)
                .reduce("", (a,b) -> a + " " + b);

// "this is a sentence"

reduce in Swift

let items = [2.0, 4.0, 5.0, 7.0]

let total = items.reduce(10.0, combine: +)

// 28.0

reduce in JavaScript

const values = [1, 4, 10, 15]

const sum = values.reduce((sumSoFar, n) => sumSoFar + n, 0) // ES2015

sum // 30
var values = [1, 4, 10, 15]

var sum = values.reduce(function (sumSoFar, n) {            // ES5
    return sumSoFar + n
}, 0)

sum // 30

reduce in Ruby

[1,2,3,4,5,6,7,8,9,10].inject{|sum,e| sum += e }
# returns 55

reduce in C#

string sentence = "the quick brown fox jumps over the lazy dog";

// Split the string into individual words.
string[] words = sentence.Split(' ');

// Prepend each word to the beginning of the 
// new sentence to reverse the word order.
string reversed = words.Aggregate((workingSentence, next) =>
                                      next + " " + workingSentence);

Console.WriteLine(reversed);

// This code produces the following output:
//
// dog lazy the over jumps fox brown quick the 

fold in F#

["a";"b";"c"] |> List.fold (+) "hello: "    
// "hello: abc"
// "hello: " + "a" + "b" + "c"

[1;2;3] |> List.fold (+) 10    
// 16
// 10 + 1 + 2 + 3

fold in Scala

val numbers = List(1, 2, 3, 4)
numbers.foldLeft(0)((m: Int, n: Int) => m + n)
// 55

reduce in Python

from functools import reduce
product = reduce( (lambda x, y: x * y), [1, 2, 3, 4] )

# Output: 24

fold in Elm

foldr (+) 0 [1,2,3] == 6

www.ling.gu.se/~lager/python_exercises.html

 

Exercises avoid using anything python specific.

Recommended!

 

Most of them involve characters, words and phrases, rather than numbers, and are therefore suitable for students interested in language rather than math.

May be quite algorithmic / tricky.
Suggested days: 1, 4, 5, 12.
Day 5 hint: Regex (.)\1 matches double chars.

Exercises

reactivex.io/learnrx

Well-explained exercises in JavaScript.

Recommended!

References

Syntax for map, filter, reduce

References

Thank you!

 
 
 
 
Questions!?
 
 
 
 
hugo.josefson@jayway.com
Code, slides: www.hugojosefson.com

Higher-order functions

  • Takes a function as argument

map :: fn -> List -> List

user = {
    name: String,
    age: Int,
    birthday: Date
}

users = [
    {
        name: "Fjodor", 
        age: 31, 
        birthday: "2009-04-21 14:25:29.5585588 UTC"
    }, 
    {
        name: "Hugo", 
        age: 40, 
        birthday: "2010-04-21 14:25:29.5585588 UTC" 
    }
]


map :: (a -> b) -> List a -> List b
map (\a -> a.birthday) users

filter :: fn -> List -> List

user = {
    name: String,
    age: Int,
    birthday: Date
}

users = [
    {
        name: "Fjodor", 
        age: 31, 
        birthday: "2009-04-21 14:25:29.5585588 UTC"
    }, 
    {
        name: "Hugo", 
        age: 40,
        birthday: "2010-04-21 14:25:29.5585588 UTC"
    }
]

filter :: (a -> Bool) -> List a -> List a
filter (\ x -> x.age `mod` 2 == 0) users

Currying

 

  • A function is applied to its arguments one at a time

 

  • Each application returns a new function that accepts the next argument

 

add

function add(a, b) {
    return a + b;
};


//Normal add function takes two arguments
node> const age = add(1, 3);
4

Curried

//What is Albins age?
//Albin is half the age of Hugo which is three years older than me

//Curried add
const add = a => b => a + b;

//Curried div
const div = b => a => a / b;

const myAge = 32;

const threeYearsOlder = add(3);

const halfTheAge = div(2);

const albinsAge = halfTheAge(threeYearsOlder(myAge))
17.5


//OR by using function composition
node> const R = require('ramda');

node> const findOutAlbinsAgeBasedOnMine = R.compose(halfTheAge, threeYearsOlder);
node> const albinsAge = findoutAlbinsAgeBasedOnMine(myAge)
17.5

Recursion

Head and Tail

node> const listOfNumbers = [ 1, 2, 3, 4, 5, 6, 7, 8 ];

node> [ head, ...tail ] = listOfNumbers;

node> head
1

node> tail
[ 2, 3, 4, 5, 6, 7, 8 ]

Recursive dinner

const dinnerName = (acc, ingredientList) => {
    if (!ingredientList.length) {               // Exit condition
      return acc;                                
    }
    const [head, ...tail] = ingredientList;     // Destructuring
    return dinnerName(head + acc, tail);        // Recursion 
};

node> const ingredients = [ 'soup', 'mushroom', 'forest' ];

node> const dinner = dinnerName('', ingredients);

node> dinner;
'forestmushroomsoup'

Exercise

create a recursive function that takes a list of numbers

and returns only even numbers

 

(hint find the modulo operator)

Pure functions

  • Idempotent
  • Does not cause side-effects

Idempotency

Always evaluates the same result value

given the same argument values

 

// Idempotency requires your function to always return the same result 
// given the same argument no matter how many times you invoke it

node> const R = require('ramda');

node> const composedIdentity = R.compose(R.identity, R.identity);

// OR

node> composedIdentity(32) === R.identity(32)
true

non-idempotent example

// NON-IDEMPOTENT
const nonIdempotentFn = arg => {
    return arg.length;
};

node> nonIdempotentFn('hello');
5

node> nonIdempotentFn(nonIdempotentFn('hello'));
undefined

node> nonIdempotentFn(5)
undefined

Side-effects

  • modifies state.
  • interacts with functions.
  • interacts with the outside world.
Made with Slides.com