intro to functional programming - confoo

Post on 15-Jul-2015

95 Views

Category:

Software

1 Downloads

Preview:

Click to see full reader

TRANSCRIPT

Functional Programming

[introduction]

Félix-Étienne Trépanier

Call me Félix- Software Engineer - Independant- Code Java / Scala (mainly)- Build Distributed Systems- Organizer of Scala-Montreal

Meetups

Agenda- Motivations- Definitions and Examples

Motivations

What makes programming so hard?

“Controlling complexity is the essence of computer programming.”- Brian Kernighan

Complexity comes from input and state.

Possible Inputs

Possible States

Possible OutcomesX =

We should aim at reducing the input and state space.

Possible Inputs

Possible States

Possible OutcomesX =

In Object-Oriented programming, everything* is an object.

Objects combine state and behavior.

Objects are state machines.

A = xB = y

A = x’B = y’

A = x’B = y

A = xB = y’

Most objects are badly designed state machines.

A = xB = y

A = x’B = y’

A = x’B = y

A = xB = y’

Large state space are hard to reason about.

Concurrency adds new visible states.

A = xB = y

A = x’B = y’

A = x’B = y

A = xB = y’

How can we write correct code if we can’t reason about it?

Let’s add more unit tests!

Let’s add more unit tests!

Definitionsand

Examples

Functional Programming imposes constraints that eliminate states and ease reasoning.

Functional Programming is about values, functions and types*.

Values

Values are immutable.

Example - Javafinal String title = "functional programming";

final Person bob = new Person("bob", 34);

class Person {

private final String name;

private final int age;

private Person(String name, int age) {

this.name = name;

this.age = age;

}

...

}

Example - Scalaval title = "functional programming"

val bob = User("bob", 34)

case class User(name: String, age: Int)

What about data structures?Can they be values?

Persistent data structures create a new updated version. They are immutable.

Example - Java (1)// using functional java

import fj.data.List;

final List<String> canadianCities =

List.list("Montreal", "Ottawa", "Toronto");

final List<String> americanCities =

List.list("New York", "San Francisco");

final List<String> northAmericanCities =

canadianCities.append(americanCities);

Example - Java (2)// using guava

final ImmutableList<String> canadianCities =

ImmutableList.of("Montreal", "Ottawa", "Toronto");

final ImmutableList<String> americanCities =

ImmutableList.of("New York", "San Francisco");

final ImmutableList<String> northAmericanCities =

ImmutableList.<String>builder().addAll(canadianCities)

.addAll(americanCities).build();

// or

final ImmutableList<String> northAmericanCities =

Stream.concat(canadianCities.stream(), americanCities.stream())

.collect(GuavaCollectors.immutableList());

Example - Scalaval canadianCities = List("Montreal", "Ottawa", "Toronto")

val americanCities = List("New York", "San Francisco")

val northAmericanCities = canadianCities ++ americanCities

Immutability eliminates state and means you can safely share the reference.

Functions

A function takes arguments and produces a result.

(Pure) Functionshave no side-effects.

Side-effects examples:- write to disk- read from stdin- throw an exception- change a state

Example - Java// pure function

public static String convert(String input) {

return new StringBuilder(input).reverse()

.toString().toUpperCase();

}

// unpure function

public static void printMessage(String message) {

if(message == null) {

throw new IllegalStateException("Should not be null");

}

System.out.println(message);

}

Example - Scala// pure function

def convert(a: String): String = {

a.reverse.toUpperCase

}

// unpure function

def print(message: String): Unit = {

if (message == null) {

throw new IllegalStateException("Should not be null!")

}

println(message)

}

Having no side-effecthelps reasoning about code.

Having no side-effect simplifies testing.

Errors are handled by returning them as results.

Example - Javapublic static Optional<Integer> parseInt(String s) {...}

// using functional java

public static Either<Exception, Integer> parse(String s) {...}

// Use combinators to avoid branching

final double discount = parseInt(ageAsString)

.map(a -> a * 0.01).orElse(0.10);

// Assume Optional<Integer> age1, age2;

final Optional<Integer> sum = age1.flatMap(a1 ->

age2.map(a2 ->

a1 + a2));

Example - Scaladef parseInt(s: String): Option[Int] = {...}

val sum: Option[Int] = for {

a <- parseInt("2")

b <- parseInt("3")

} yield a + b

Updating a state is done by creating a new instance with the updated state.

For other necessary side-effects, push them on the boundaries.

Looping can be implemented with recursion*.

Example - Scaladef sumAllFrom(value: Int): Int = {

// always use the @tailrec annotation

// tail recursive functions can be optimized to a loop

@tailrec

def internalSum(value: Int, currentSum: Int): Int = {

if (value == 0) {

currentSum

} else {

internalSum(value - 1, currentSum + value)

}

}

internalSum(value, 0)

}

A high-order functions takes functions as parameters and/or produces a function.

Example - Javafinal ImmutableList<Integer> l = ImmutableList.of(1, 2, 3, 4);

final ImmutableList<Integer> mapped = l.stream()

.filter(e -> (e % 2) == 0)

.map(e -> e * 2)

.collect(GuavaCollectors.immutableList());

Example - Scalaval l = List(1, 2, 3, 4)

val mapped = l.filter(e => e % 2 == 0).map(_ * 2)

val mappedStream = l.toStream

.filter(e => e % 2 == 0)

.map(_ * 2)

.toList

Example - Javafinal Function<Integer, Integer> doubled = e -> e * 2;

final Function<Integer, Integer> increment = e -> e + 1;

final Function<Integer, Integer> doubleAndIncrement =

doubled.andThen(increment);

Types

Types define classifications of values.

With types, the compilercan verify some aspects of correctness.

Example - Scalacase class User(name: String, city: String, phoneNumber: String)

// ?!

val user = User("Montreal", "John", "New York")

// These types could include validation

// so any object successfully created is valid.

// Check value classes to avoid garbage

trait Name {...}

trait City {...}

trait PhoneNumber {...}

case class User(name: Name, city: City, phoneNumber:PhoneNumber)

Meta

Object-Oriented is about semantic.

Functional Programming is about shape.

Example - Javainterface Process

interface ProcessConfig

interface ProcessFactory {

Process createProcess(ProcessConfig config)

}

interface Machine

interface MachineConfig

interface MachineFactory {

Machine createMachine(MachineConfig config)

}

Example - Javainterface Process

interface ProcessConfig

interface Machine

interface MachineConfig

interface Factory<T, C> {

T create(C config)

}

interface ProcessFactory extends Factory<Process, ProcessConfig>

interface MachineFactory extends Factory<Machine, MachineConfig>

Example - Javainterface Process

interface ProcessConfig

interface Machine

interface MachineConfig

interface Factory<T, C> {

T create(C config)

}

interface ProcessFactory extends Factory<Process, ProcessConfig>

interface MachineFactory extends Factory<Machine, MachineConfig>

Function<C, T>

Reuse by shape is more powerful than reuse by semantic.

BUT understanding shape is harder than understanding semantic.

Summary

Summary

- Functional programing: immutable values, pure functions and precise types*.

- Benefits are:- Simple code- Concurrency and Parallelism for free- Easy to reason about- Reusable

ContactFélix-Étienne Trépanier

Twitter - @felixtrepanierGithub - @coderunner

unconfoo session @ 2pm!

top related