Introduction à la programmation fonctionnelle

Enseignant

Sébastian Le Merdy

19 ans d’expérience professionnelle
dans le développement

ESN 😮

Xebia
Société Générale Colissimo Libon Vidal Michelin Bisam
Elosi
Rsi GMF

Plus récemment dans un organisme de paiement

Nickel

Actuellement chez un éditeur SaaS
dans la data

Zeenea

Principalement autour des langages JVM

Java Scala

Mais rentrons dans le vif du sujet

FP…

Fonction

Définition mathématique

E → F
x ↦ f(x)

Il s’agit d’une transformation
d’une valeur appartement à l’ensemble de départ
vers une valeur appartenant à l’ensemble d’arrivée.

Caractéristiques d’une fonction

  • totale
  • déterministe
  • sans effet de bord

Pureté

def sayHello(niceMessage: String): Unit =
  println(s"Hello $niceMessage")
sayHello("world!")
Hello world!
def dice(): Int =
  Random.nextInt(6) + 1
dice()
val res0: Int = 5
dice()
val res1: Int = 3
def nowPlus(days: Long): Instant =
  Instant.now().plus(days, ChronoUnit.DAYS)
nowPlus(3)
val res0: java.time.Instant = 2024-03-05T18:55:34.125784Z
nowPlus(5)
val res1: java.time.Instant = 2024-03-07T18:55:34.215660Z
def saveArticle(id: UUID, content: String, author: Author)(
    database: Database
): Boolean =
  database.executeQuery(
    """INSERT INTO Article(id, content, author_name, author_email)
      |VALUES (:id, :content, :author_name, :author_email);
      |""".stripMargin,
    "id" -> id,
    "content" -> content,
    "author_name" -> author.name,
    "author_email" -> author.email
  )
saveArticle(
    UUID.fromString("0d975fff-44dc-4110-b21a-1f31148969b8"),
    "<p>Great content</p>",
    Author("Sébastian", "seb@example.com")
  )(postgresql)
val res0: Boolean = true

Toutes ces fonctions sont impures car :

  • deux appels successifs ne produisent pas la même valeur en retour ;
  • elles créent des effets de bord.

Pourquoi les fonctions pures sont-elles intéressantes ?

  • transparence référentielle
  • immuabilité
    • des paramètres en entrée
    • de la valeur produite en retour

Permet donc des optimisations massivement parallélisables

Composition

Définition mathématique

f ∘ g

f(g(x))

D’un point de vue génie logiciel

Il s’agit d’un moyen de garantir

  1. la ré-utilisabilité du code
  2. le bon niveau d’abstraction
  3. la lisibilité
case class User(name: String, age: Int)
enum Category:
  case Young, Adult, Old
val categoryByMaxAges: Map[Int, Category] =
  Map(
    24 -> Category.Young,
    64 -> Category.Adult,
    Integer.MAX_VALUE -> Category.Old
  )
val discountByCategories: Map[Category, Int] =
  Map(
    Category.Young -> 25,
    Category.Adult -> 0,
    Category.Old -> 35
  )
def computePrice(
    user: User,
    categories: Map[Int, Category],
    price: Long,
    discounts: Map[Category, Int]
): Long = price - price * discounts(
  categories
    .filter { case (maxAge, category) => maxAge >= user.age }
    .minBy { case (maxAge, category) => maxAge }
    ._2
) / 100
computePrice(          computePrice(          computePrice(
  User("Emma", 16),      User("Nicolas", 41),   User("Martine", 67),
  categoryByMaxAges,     categoryByMaxAges,     categoryByMaxAges,
  price = 1999,          price = 5500,          price = 4999,
  discountByCategories   discountByCategories   discountByCategories
)                      )                      )
val res0: Long = 1500  val res1: Long = 5500  val res2: Long = 3250

Décomposons


def computePrice(
    user: User,
    categories: Map[Int, Category],
    price: Long,
    discounts: Map[Category, Int]
): Long =
  computePrice(
    price,
    computeDiscount(user, categories, discounts)
  )
						

def computePrice(price: Long, discount: Long): Long =
  price - price * discount / 100
						

def computeDiscount(
    user: User,
    categories: Map[Int, Category],
    discounts: Map[Category, Int]
): Long = discounts(findCategory(user, categories))
						

def findCategory(
    user: User,
    categories: Map[Int, Category]
): Category =
  val (_, category) = categories
    .filter { case (maxAge, category) => maxAge >= user.age }
    .minBy { case (maxAge, category) => maxAge }
  category
						

Les fonctions : ces citoyens de premier ordre

Une fonction est considérée comme un type de données comme les autres.

  • peut-être passée en argument d’une autre fonction
def logBeforeAndAfter(message: String, task: () => Unit): Unit =
  println(s"before $message")
  task()
  println(s" after $message")
logBeforeAndAfter("hello", () => { println("Hello World!") })
before hello
Hello World!
 after hello
def logBeforeAndAfterResult(
    message: String,
    computeResult: String => Long
): Unit =
  println(s"before $message")
  println(s"result ${computeResult(message)}")
  println(s" after $message")
logBeforeAndAfterResult("hello", message => message.length)
before hello
result 5
 after hello

Une fonction est considérée comme un type de données comme les autres.

  • peut-être passée en argument d’une autre fonction
  • peut-être retournée par une autre fonction
def operationToCompute(operation: String): (Long, Long) => Long =
  operation match
    case "add"      => (left, right) => left + right
    case "subtract" => (left, right) => left - right
    case "multiply" => (left, right) => left * right
    case _          => (left, right) => Long.MinValue
operationToCompute("add")(3, 2)
val res0: Long = 5
operationToCompute("subtract")(3, 2)
val res1: Long = 1
operationToCompute("multiply")(3, 2)
val res2: Long = 6
operationToCompute("divide"  )(3, 2)
val res3: Long = -9223372036854775808

Récursivité

La possibilité pour une fonction de se rappeler elle-même. Permet de traiter :

  • une structure de données elle-même récursive
  • une collection

Implémentation

  1. trouver une ou plusieurs conditions d’arrêt
  2. se rappeler soi-même avec des données transformées

Récursion terminale

L’appel récursif est la dernière évaluation dans l’implémentation de la fonction

Permet d’éviter des épuisements de la pile d’exécution (aka StackOverflow®)

Exemple: lire un flux

val input = """34
              |84
              |12
              |
              |32
              |28
              |9
              |7""".stripMargin
@tailrec
def sums(
    input: List[String],
    acc: Seq[Long] = Vector.empty,
    sum: Long = 0
): Seq[Long] =
  input match
    case Nil        => acc :+ sum
    case "" :: tail => sums(tail, acc :+ sum)
    case n :: tail  => sums(tail, acc, sum + n.toLong)
sums(input.linesIterator.toList)
val res0: Seq[Long] = Vector(130, 76)

Introduction à Scala

Présentation fonctionnelle de Scala

Point of View

Press ESC to enter the slide overview.

Hold down the alt key (ctrl in Linux) and click on any element to zoom towards it using zoom.js. Click again to zoom back out.

(NOTE: Use ctrl + click in Linux.)

Auto-Animate

Automatically animate matching elements across slides with Auto-Animate.

Auto-Animate

Auto-Animate

Touch Optimized

Presentations look great on touch devices, like mobile phones and tablets. Simply swipe through your slides.

Add the r-fit-text class to auto-size text

FIT TEXT

Fragments

Hit the next arrow...

... to step through ...

... a fragmented slide.

Fragment Styles

There's different types of fragments, like:

grow

shrink

fade-out

fade-right, up, down, left

fade-in-then-out

fade-in-then-semi-out

Highlight red blue green

Transition Styles

You can select from different transitions, like:
None - Fade - Slide - Convex - Concave - Zoom

Themes

reveal.js comes with a few themes built in:
Black (default) - White - League - Sky - Beige - Simple
Serif - Blood - Night - Moon - Solarized

Slide Backgrounds

Set data-background="#dddddd" on a slide to change the background color. All CSS color formats are supported.

Down arrow

Gradient Backgrounds

<section data-background-gradient=
							"linear-gradient(to bottom, #ddd, #191919)">

Image Backgrounds

<section data-background="image.png">

Tiled Backgrounds

<section data-background="image.png" data-background-repeat="repeat" data-background-size="100px">

Video Backgrounds

<section data-background-video="video.mp4,video.webm">

... and GIFs!

Background Transitions

Different background transitions are available via the backgroundTransition option. This one's called "zoom".

Reveal.configure({ backgroundTransition: 'zoom' })

Background Transitions

You can override background transitions per-slide.

<section data-background-transition="zoom">

Iframe Backgrounds

Since reveal.js runs on the web, you can easily embed other web content. Try interacting with the page in the background.

Marvelous List

  • No order here
  • Or here
  • Or here
  • Or here

Fantastic Ordered List

  1. One is smaller than...
  2. Two is smaller than...
  3. Three!

Tabular Tables

Item Value Quantity
Apples $1 7
Lemonade $2 18
Bread $3 2

Clever Quotes

These guys come in two forms, inline: The nice thing about standards is that there are so many to choose from and block:

“For years there has been a theory that millions of monkeys typing at random on millions of typewriters would reproduce the entire works of Shakespeare. The Internet has proven this theory to be untrue.”

Intergalactic Interconnections

You can link between slides internally, like this.

Speaker View

There's a speaker view. It includes a timer, preview of the upcoming slide as well as your speaker notes.

Press the S key to try it out.

Export to PDF

Presentations can be exported to PDF, here's an example:

Global State

Set data-state="something" on a slide and "something" will be added as a class to the document element when the slide is open. This lets you apply broader style changes, like switching the page background.

State Events

Additionally custom events can be triggered on a per slide basis by binding to the data-state name.


Reveal.on( 'customevent', function() {
	console.log( '"customevent" has fired' );
} );
					

Take a Moment

Press B or . on your keyboard to pause the presentation. This is helpful when you're on stage and want to take distracting slides off the screen.

Much more

THE END

- Try the online editor
- Source code & documentation