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 Outcomes X =
We should aim at reducing the input and state space. Possible Inputs Possible States Possible Outcomes X =
In Object-Oriented programming, everything* is an object.
Objects combine state and behavior.
Objects are state machines. A = x B = y A = x’ B = y’ A = x’ B = y A = x B = y’
Most objects are badly designed state machines. A = x B = y A = x’ B = y’ A = x’ B = y A = x B = y’
Large state space are hard to reason about.
Concurrency adds new visible states. A = x B = y A = x’ B = y’ A = x’ B = y A = x B = 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!
Definitions and Examples
Functional Programming imposes constraints that eliminate states and ease reasoning.
Functional Programming is about values, functions and types*.
Values
Values are immutable.
Example - Java final 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 - Scala val 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 - Scala val 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) Functions have 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-effect helps reasoning about code.
Having no side-effect simplifies testing.
Errors are handled by returning them as results.
Example - Java public 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 - Scala def 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 - Scala def 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 - Java final 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 - Scala val 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 - Java final 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 compiler can verify some aspects of correctness.
Example - Scala case 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 - Java interface Process interface ProcessConfig interface ProcessFactory { Process createProcess(ProcessConfig config) } interface Machine interface MachineConfig interface MachineFactory { Machine createMachine(MachineConfig config) }
Example - Java interface 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 - Java interface 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
Contact Félix-Étienne Trépanier Twitter - @felixtrepanier Github - @coderunner unconfoo session @ 2pm!
Images https://www.flickr.com/photos/vestman/ https://www.flickr.com/photos/arenamontanus/

Intro to functional programming - Confoo

  • 1.
  • 2.
    Call me Félix -Software Engineer - Independant - Code Java / Scala (mainly) - Build Distributed Systems - Organizer of Scala-Montreal Meetups
  • 3.
  • 4.
  • 5.
  • 6.
    “Controlling complexity is theessence of computer programming.” - Brian Kernighan
  • 7.
    Complexity comes from inputand state. Possible Inputs Possible States Possible Outcomes X =
  • 8.
    We should aimat reducing the input and state space. Possible Inputs Possible States Possible Outcomes X =
  • 9.
  • 10.
    Objects combine stateand behavior.
  • 11.
    Objects are statemachines. A = x B = y A = x’ B = y’ A = x’ B = y A = x B = y’
  • 12.
    Most objects arebadly designed state machines. A = x B = y A = x’ B = y’ A = x’ B = y A = x B = y’
  • 13.
    Large state spaceare hard to reason about.
  • 14.
    Concurrency adds new visiblestates. A = x B = y A = x’ B = y’ A = x’ B = y A = x B = y’
  • 15.
    How can wewrite correct code if we can’t reason about it?
  • 16.
    Let’s add moreunit tests!
  • 17.
    Let’s add moreunit tests!
  • 18.
  • 19.
    Functional Programming imposes constraintsthat eliminate states and ease reasoning.
  • 20.
    Functional Programming is aboutvalues, functions and types*.
  • 21.
  • 22.
  • 23.
    Example - Java finalString 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; } ... }
  • 24.
    Example - Scala valtitle = "functional programming" val bob = User("bob", 34) case class User(name: String, age: Int)
  • 25.
    What about datastructures? Can they be values?
  • 26.
    Persistent data structures createa new updated version. They are immutable.
  • 27.
    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);
  • 28.
    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());
  • 29.
    Example - Scala valcanadianCities = List("Montreal", "Ottawa", "Toronto") val americanCities = List("New York", "San Francisco") val northAmericanCities = canadianCities ++ americanCities
  • 30.
    Immutability eliminates state andmeans you can safely share the reference.
  • 31.
  • 32.
    A function takesarguments and produces a result. (Pure) Functions have no side-effects.
  • 33.
    Side-effects examples: - writeto disk - read from stdin - throw an exception - change a state
  • 34.
    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); }
  • 35.
    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) }
  • 36.
    Having no side-effect helpsreasoning about code.
  • 37.
  • 38.
    Errors are handledby returning them as results.
  • 39.
    Example - Java publicstatic 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));
  • 40.
    Example - Scala defparseInt(s: String): Option[Int] = {...} val sum: Option[Int] = for { a <- parseInt("2") b <- parseInt("3") } yield a + b
  • 41.
    Updating a stateis done by creating a new instance with the updated state.
  • 42.
    For other necessaryside- effects, push them on the boundaries.
  • 43.
    Looping can beimplemented with recursion*.
  • 44.
    Example - Scala defsumAllFrom(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) }
  • 45.
    A high-order functionstakes functions as parameters and/or produces a function.
  • 46.
    Example - Java finalImmutableList<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());
  • 47.
    Example - Scala vall = 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
  • 48.
    Example - Java finalFunction<Integer, Integer> doubled = e -> e * 2; final Function<Integer, Integer> increment = e -> e + 1; final Function<Integer, Integer> doubleAndIncrement = doubled.andThen(increment);
  • 49.
  • 50.
  • 51.
    With types, thecompiler can verify some aspects of correctness.
  • 52.
    Example - Scala caseclass 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)
  • 53.
  • 54.
  • 55.
    Example - Java interfaceProcess interface ProcessConfig interface ProcessFactory { Process createProcess(ProcessConfig config) } interface Machine interface MachineConfig interface MachineFactory { Machine createMachine(MachineConfig config) }
  • 56.
    Example - Java interfaceProcess 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>
  • 57.
    Example - Java interfaceProcess 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>
  • 58.
  • 59.
    Reuse by shapeis more powerful than reuse by semantic.
  • 60.
    BUT understanding shapeis harder than understanding semantic.
  • 61.
  • 62.
    Summary - Functional programing:immutable values, pure functions and precise types*. - Benefits are: - Simple code - Concurrency and Parallelism for free - Easy to reason about - Reusable
  • 63.
    Contact Félix-Étienne Trépanier Twitter -@felixtrepanier Github - @coderunner unconfoo session @ 2pm!
  • 64.