La programmation fonctionnelle, c’est juste pour faire des preuves ?

Julien Richard-Foy

http://julienrf.github.io/breizhcamp-2016

BreizhCamp 2016

Plan

Composition

Compositionnalité

Principe selon lequel la signification d’une expression complexe est définie par les significations des expressions la composant, et par les règles employées pour les combiner.

Wikipedia

La composition appliquée aux programmes

La composition appliquée aux programmes (variante)

IllegalStateException

(existe aussi sous la forme d’une NullPointerException, IOException, etc.)

User user = new User();
println(user.name);
Invalide

Invalide

Toute valeur doit être initialisée dans un état valide

User user = new User("Julien");
doSomething(user);
println(user.name);
Crush

Crush

La valeur associée à un nom donné ne doit pas changer au cours du temps

Immuabilité

Integer n = 1;
n + n == 1 + 1;
Image img = new Circle(5);
img.beside(img) == (new Circle(5)).beside(new Circle(5));

Alias vs copie superficielle vs copie profonde

Image ear = new Circle(8);
Image face = new Circle(10);
Image mickey =
  ear.beside(ear)
    .above(face);
Valeurs

Valeurs

Travailler avec des valeurs

ie. Éviter de ne « rien » retourner

Composition(nalité) : synthèse

Modélisation

When you program you have to think about how someone will read your code, not just how a computer will interpret it.

Kent Beck

Partition

Partition

Partition de musique (en français)

Products

Products

public class Note {
  public Name name;
  public Rhythm rhythm;
}
public class Note {
  public final Name name;
  public final Rhythm rhythm;

  public Note(Name name, Rhythm rhythm) {
    this.name = name;
    this.rhythm = rhythm;
  }
}
Note do1 = new Note(C, Quarter);
Note do2 = new Note(C, Quarter);
assert(do1.equals(do2)); // ???

Value Classes

public final class Note {
  public final Name name;
  public final Rhythm rhythm;

  public Note(Name name, Rhythm rhythm) {
    this.name = name;
    this.rhythm = rhythm;
  }

  @Override
  public Boolean equals(that) {
    if (that instanceof Note) {
      return this.name.equals(that.name) && this.rhythm.equals(that.rhythm);
    } else return false;
  }

  @Override
  public Long hashCode() {
    return (17 + name.hashCode()) * 31 + rhythm.hashCode(); 
  }
}
Note

Note

Scala :

case class Note(
  name: Name,
  rhythm: Rhythm
)

Haskell :

data Note = Note Name Rhythm

« Un symbole musical est soit un silence, soit une note »

Sums

Sums

Inheritance

Inheritance

Visitor

Visitor

public abstract class Symbol {
  private Symbol() {}

  public abstract void accept(Visitor visitor);

  public static final class Note extends Symbol {
    …
    @Override
    public void accept(Visitor visitor) {
      visitor.visitNote(this);
    }
  }
  public static final class Rest extends Symbol {
    …
    @Override
    public void accept(Visitor visitor) {
      visitor.visitRest(this);
    }
  }

  public static interface Visitor {
    void visitNote(Note note);
    void visitRest(Rest rest);
  }
}
Sums

Sums

Sum Types

a.k.a. union types

Scala :

sealed trait Symbol
case class Note(name: Name, rhythm: Rhythm) extends Symbol
case class Rest(rhythm: Rhythm) extends Symbol
def doSomething(symbol: Symbol): Unit =
  symbol match {
    case Note(name, rhythm) => …
    case Rest(rhythm) => …
  }

Haskell :

data Symbol = Note Name Rhythm | Rest Rhythm
doSomething (Note name rhythm) = 
doSomething (Rest rhythm) = 

Modélisation : synthèse

Abstraction

Définition

The purpose of abstraction is not to be vague, but to create a new semantic level in which one can be absolutely precise

Edsger W. Dijkstra

On est déjà à mi-chemin !

Principe d’abstraction

Each significant piece of functionality in a program should be implemented in just one place in the source code.

Where similar functions are carried out by distinct pieces of code, it is generally beneficial to combine them into one by abstracting out the varying parts.

Benjamin C. Pierce

Double earArea = earRadius * earRadius * Math.PI;
Double faceArea = faceRadius * faceRadius * Math.PI;
Double earArea = earRadius * earRadius * Math.PI;
Double faceArea = faceRadius * faceRadius * Math.PI;
public Double disqArea(Double radius) {
  return radius * radius * Math.PI;
}

Généraliser une procédure permet de prendre du recul

Double earArea = earRadius * earRadius * Math.PI;
Double faceArea = faceRadius * faceRadius * Math.PI;
public Double disqArea(Double radius) {
  return radius * radius * Math.PI;
}
Double earArea = disqArea(earRadius);
Double faceArea = disqArea(faceRadius);
public Double disqArea(Double radius) {
  return radius * radius * Math.PI;
}

Nommer les choses améliore la lisibilité

public Integer sum(List<Integer> numbers) {
  Integer result = 0;
  for (Integer n : numbers) {
    result = result + n;
  }
  return result;
}
public Integer product(List<Integer> numbers) {
  Integer result = 1;
  for (Integer n : numbers) {
    result = result * n;
  }
  return result;
}
public Integer reduce(
    List<Integer> numbers,
    Integer init,
    BiFunction<Integer, Integer, Integer> op
) {
  Integer result = init;
  for (Integer n : numbers) {
    result = op.apply(result, n)
  }
  return result;
}
public Integer sum(List<Integer> numbers) {
  return reduce(numbers, 0, (n1, n2) -> n1 + n2);
}
public Integer product(List<Integer> numbers) {
  return reduce(numbers, 1, (n1, n2) -> n1 * n2);
}
public Image allBeside(List<Image> images) {
  Image result = Image.EMPTY;
  for (Image image : images) {
    result = result.beside(image);
  }
  return result;
}
public Image allBeside(List<Image> images) {
  return reduce(images, Image.EMPTY, (i1, i2) -> i1.beside(i2));
}

Scala :

def reduce(numbers: List[Int], init: Int, op: (Int, Int) => Int): Int
def sum(numbers: List[Int]): Int =
  reduce(numbers, 0, (x, y) => x + y);

Haskell :

reduce :: Integer -> (Integer -> Integer -> Integer) -> [Integer] -> Integer
reduce init op numbers = 
sum :: [Integer] -> Integer
sum = reduce 0 (\x y -> x + y)

(Principe d’)abstraction : synthèse

Applications pratiques

Architectures

Tirer parti de ces ressources

Paralléliser

Super-Lambda

Super-Lambda

Gérer la concurrence

Super-Lambda

Super-Lambda

Gérer l’intermittence des traitements

Super-Lambda

Super-Lambda

Logiciels

Modéliser et traiter de l’information

Super-Lambda

Super-Lambda

Résumé

La programmation fonctionnelle est pratique pour :

Questions ?

julien@richard-foy.fr

Bonus

Persistent data structures

Persistent

Persistent

Inconvénients de l’immuabilité

Langage fonctionnel ?

Java

interface Optional<T> {
  public <U> Optional<U> map(Function<? super T, ? extends U> mapper);
}

Scala

trait Option[A] {
  def map[B](f: A => B): Option[B]
}

Haskell

fmap :: (a -> b) -> Maybe a -> Maybe b