Partager cette page

Apprendre X en Y minutes

Où X=F#

F# est un langage de programmation fonctionnel et orienté objet. Il est gratuit et son code source est ouvert. Il tourne sur Linux, Mac, Windows et plus.

Il possède un puissant système de type qui piège de nombreuses erreurs à la compilation, mais il utilise l'inférence de type ce qui lui permet d'être lu comme un langage dynamique.

La syntaxe de F# est différente des langages héritant de C.

Si vous voulez essayer le code ci-dessous, vous pouvez vous rendre sur tryfsharp.org et le coller dans le REPL.

// Les commentaires d'une seule ligne commencent par un double slash
(* Les commentaires multilignes utilise les paires (* . . . *)

-fin du commentaire multilignes- *)

// ================================================
// Syntaxe de base
// ================================================

// ------ "Variables" (mais pas vraiment) ------
// Le mot clé "let" définit une valeur (immutable)
let myInt = 5
let myFloat = 3.14
let myString = "hello"           // Notons qu'aucun type n'est nécessaire

// ------ Listes ------
let twoToFive = [2;3;4;5]        // Les crochets créent une liste avec
                                 // des point-virgules pour délimiteurs.
let oneToFive = 1 :: twoToFive   // :: crée une liste avec un nouvel élément
// Le résultat est [1;2;3;4;5]
let zeroToFive = [0;1] @ twoToFive   // @ concatène deux listes

// IMPORTANT: les virgules ne sont jamais utilisées pour délimiter,
// seulement les point-virgules !

// ------ Fonctions ------
// Le mot clé "let" définit aussi le nom d'une fonction.
let square x = x * x          // Notons qu'aucune parenthèse n'est utilisée.
square 3                      // Maitenant, exécutons la fonction.
                              // Encore une fois, aucune parenthèse.

let add x y = x + y           // N'utilisez pas add (x,y) ! Cela signifie
                              // quelque chose de complètement différent.
add 2 3                       // À présent, exécutons la fonction.

// Pour définir une fonction sur plusieurs lignes, utilisons l'indentation.
// Les point-virgules ne sont pas nécessaires.
let evens list =
   let isEven x = x%2 = 0     // Définit "isEven" comme une fonction imbriquée
   List.filter isEven list    // List.filter est une fonction de la librairie
                              // à deux paramètres: un fonction retournant un
                              // booléen et une liste sur laquelle travailler

evens oneToFive               // À présent, exécutons la fonction.

// Vous pouvez utilisez les parenthèses pour clarifier.
// Dans cet exemple, "map" est exécutée en première, avec deux arguments,
// ensuite "sum" est exécutée sur le résultat.
// Sans les parenthèses, "List.map" serait passé en argument à List.sum.
let sumOfSquaresTo100 =
   List.sum ( List.map square [1..100] )

// Vous pouvez rediriger la sortie d'une fonction vers une autre avec "|>"
// Rediriger des données est très commun en F#, comme avec les pipes UNIX.

// Voici la même fonction sumOfSquares écrite en utilisant des pipes
let sumOfSquaresTo100piped =
   [1..100] |> List.map square |> List.sum  // "square" est déclaré avant

// Vous pouvez définir des lambdas (fonctions anonymes) grâce au mot clé "fun"
let sumOfSquaresTo100withFun =
   [1..100] |> List.map (fun x -> x*x) |> List.sum

// En F#, il n'y a pas de mot clé "return". Une fonction retourne toujours
// la valeur de la dernière expression utilisée.

// ------ Pattern Matching ------
// Match..with.. est une surcharge de la condition case/switch.
let simplePatternMatch =
   let x = "a"
   match x with
    | "a" -> printfn "x is a"
    | "b" -> printfn "x is b"
    | _ -> printfn "x is something else"   // underscore correspond à tout le reste

// F# n'autorise pas la valeur null par défaut -- vous devez utiliser le type Option
// et ensuite faire correspondre le pattern.
// Some(..) et None sont approximativement analogue à des wrappers de Nullable
let validValue = Some(99)
let invalidValue = None

// Dans cet exemple, match..with trouve une correspondance à "Some" et à "None",
// et affiche la valeur du "Some" en même temps.
let optionPatternMatch input =
   match input with
    | Some i -> printfn "input is an int=%d" i
    | None -> printfn "input is missing"

optionPatternMatch validValue
optionPatternMatch invalidValue

// ------ Affichage ------
// Les fonctions printf/printfn sont similaires aux fonctions
// Console.Write/WriteLine de C#.
printfn "Printing an int %i, a float %f, a bool %b" 1 2.0 true
printfn "A string %s, and something generic %A" "hello" [1;2;3;4]

// Il y a aussi les fonctions printf/sprintfn pour formater des données
// en string. C'est similaire au String.Format de C#.

// ================================================
// Plus sur les fonctions
// ================================================

// F# est un véritable langage fonctionel -- les fonctions sont des
// entités de premier ordre et peuvent êtres combinées facilement
// pour créer des constructions puissantes

// Les modules sont utilisés pour grouper des fonctions ensemble.
// L'indentation est nécessaire pour chaque module imbriqué.
module FunctionExamples =

    // définit un simple fonction d'addition
    let add x y = x + y

    // usage basique d'une fonction
    let a = add 1 2
    printfn "1+2 = %i" a

    // application partielle des paramètres (curryfication ou "currying" en anglais)
    // add42 est une nouvelle fonction qui ne prend plus qu'un paramètre
    let add42 = add 42
    let b = add42 1
    printfn "42+1 = %i" b

    // composition pour combiner des fonctions
    let add1 = add 1
    let add2 = add 2
    let add3 = add1 >> add2
    let c = add3 7
    printfn "3+7 = %i" c

    // fonctions de premier ordre
    [1..10] |> List.map add3 |> printfn "new list is %A"

    // listes de fonction et plus
    let add6 = [add1; add2; add3] |> List.reduce (>>)
    let d = add6 7
    printfn "1+2+3+7 = %i" d

// ================================================
// Listes et collections
// ================================================

// Il y a trois types de collection ordonnée :
// * Les listes sont les collections immutables les plus basiques
// * Les tableaux sont mutables et plus efficients
// * Les séquences sont lazy et infinies (e.g. un enumerator)
//
// Des autres collections incluent des maps immutables et des sets
// plus toutes les collections de .NET

module ListExamples =

    // les listes utilisent des crochets
    let list1 = ["a";"b"]
    let list2 = "c" :: list1    // :: pour un ajout au début
    let list3 = list1 @ list2   // @ pour la concatenation

    // Compréhensions des listes (aka générateurs)
    let squares = [for i in 1..10 do yield i*i]

    //  Générateur de nombre premier
    let rec sieve = function
        | (p::xs) -> p :: sieve [ for x in xs do if x % p > 0 then yield x ]
        | []      -> []
    let primes = sieve [2..50]
    printfn "%A" primes

    // le pattern matching pour les listes
    let listMatcher aList =
        match aList with
        | [] -> printfn "the list is empty"
        | [first] -> printfn "the list has one element %A " first
        | [first; second] -> printfn "list is %A and %A" first second
        | _ -> printfn "the list has more than two elements"

    listMatcher [1;2;3;4]
    listMatcher [1;2]
    listMatcher [1]
    listMatcher []

    // Récursion en utilisant les listes
    let rec sum aList =
        match aList with
        | [] -> 0
        | x::xs -> x + sum xs
    sum [1..10]

    // -----------------------------------------
    // Fonctions de la librairie standard
    // -----------------------------------------

    // map
    let add3 x = x + 3
    [1..10] |> List.map add3

    // filtre
    let even x = x % 2 = 0
    [1..10] |> List.filter even

    // beaucoup plus -- se référer à la documentation

module ArrayExamples =

    // les tableaux utilisent les crochets avec des barres
    let array1 = [| "a";"b" |]
    let first = array1.[0]        // accès à l'index en utilisant un point

    // le pattern matching des tableaux est le même que celui des listes
    let arrayMatcher aList =
        match aList with
        | [| |] -> printfn "the array is empty"
        | [| first |] -> printfn "the array has one element %A " first
        | [| first; second |] -> printfn "array is %A and %A" first second
        | _ -> printfn "the array has more than two elements"

    arrayMatcher [| 1;2;3;4 |]

    // Fonctions de la librairie standard comme celles des listes
    [| 1..10 |]
    |> Array.map (fun i -> i+3)
    |> Array.filter (fun i -> i%2 = 0)
    |> Array.iter (printfn "value is %i. ")

module SequenceExamples =

    // Les séquences utilisent des accolades
    let seq1 = seq { yield "a"; yield "b" }

    // Les séquences peuvent utiliser yield et
    // peuvent contenir des sous-sequences
    let strange = seq {
        // "yield" ajoute un élément
        yield 1; yield 2;

        // "yield!" ajoute une sous-sequence complète
        yield! [5..10]
        yield! seq {
            for i in 1..10 do
              if i%2 = 0 then yield i }}
    // test
    strange |> Seq.toList

    // Les séquences peuvent être créées en utilisant "unfold"
    // Voici la suite de fibonacci
    let fib = Seq.unfold (fun (fst,snd) ->
        Some(fst + snd, (snd, fst + snd))) (0,1)

    // test
    let fib10 = fib |> Seq.take 10 |> Seq.toList
    printf "first 10 fibs are %A" fib10

// ================================================
// Types de données
// ================================================

module DataTypeExamples =

    // Toutes les données sont immutables par défaut

    // Les tuples sont de simple et rapide types anonymes
    // -- Utilisons une virgule pour créer un tuple
    let twoTuple = 1,2
    let threeTuple = "a",2,true

    // Pattern match pour déballer
    let x,y = twoTuple  // assigne x=1 y=2

    // ------------------------------------
    // Record types ont des champs nommés
    // ------------------------------------

    // On utilise "type" avec des accolades pour définir un type record
    type Person = {First:string; Last:string}

    // On utilise "let" avec des accolades pour créer un record (enregistrement)
    let person1 = {First="John"; Last="Doe"}

    // Pattern match pour déballer
    let {First=first} = person1    // assigne first="john"

    // ------------------------------------
    // Union types (ou variants) ont un set (ensemble) de choix
    // Un seul cas peut être valide à la fois.
    // ------------------------------------

    // On utilise "type" avec bar/pipe pour definir un union type
    type Temp =
        | DegreesC of float
        | DegreesF of float

    // On utilise un de ces choix pour en créér un
    let temp1 = DegreesF 98.6
    let temp2 = DegreesC 37.0

    // Pattern match on all cases to unpack(?)
    let printTemp = function
       | DegreesC t -> printfn "%f degC" t
       | DegreesF t -> printfn "%f degF" t

    printTemp temp1
    printTemp temp2

    // ------------------------------------
    // Types récursif
    // ------------------------------------

    // Les types peuvent être combinés récursivement de façon complexe
    // sans avoir à créer des sous-classes
    type Employee =
      | Worker of Person
      | Manager of Employee list

    let jdoe = {First="John";Last="Doe"}
    let worker = Worker jdoe

    // ------------------------------------
    // Modelling with types(?)
    // ------------------------------------

    // Les types union sont excellents pour modelling state without using flags(?)
    type EmailAddress =
        | ValidEmailAddress of string
        | InvalidEmailAddress of string

    let trySendEmail email =
        match email with // utilisations du pattern matching
        | ValidEmailAddress address -> ()   // envoyer
        | InvalidEmailAddress address -> () // ne pas envoyer

    // Combiner ensemble, les types union et les types record
    // offrent une excellente fondation pour le domain driven design.
    // Vous pouvez créer des centaines de petit types qui reflèteront fidèlement
    // le domain.

    type CartItem = { ProductCode: string; Qty: int }
    type Payment = Payment of float
    type ActiveCartData = { UnpaidItems: CartItem list }
    type PaidCartData = { PaidItems: CartItem list; Payment: Payment}

    type ShoppingCart =
        | EmptyCart  // aucune donnée
        | ActiveCart of ActiveCartData
        | PaidCart of PaidCartData

    // ------------------------------------
    // Comportement natif des types
    // ------------------------------------

    // Les types natifs ont un comportement "prêt-à-l'emploi" des plus utiles, sans code à ajouter.
    // * Immutabilité
    // * Pretty printing au debug
    // * Egalité et comparaison
    // * Sérialisation

    // Le Pretty printing s'utilise avec %A
    printfn "twoTuple=%A,\nPerson=%A,\nTemp=%A,\nEmployee=%A"
             twoTuple person1 temp1 worker

    // L'égalité et la comparaison sont innés
    // Voici un exemple avec des cartes.
    type Suit = Club | Diamond | Spade | Heart
    type Rank = Two | Three | Four | Five | Six | Seven | Eight
                | Nine | Ten | Jack | Queen | King | Ace

    let hand = [ Club,Ace; Heart,Three; Heart,Ace;
                 Spade,Jack; Diamond,Two; Diamond,Ace ]

    // tri
    List.sort hand |> printfn "sorted hand is (low to high) %A"
    List.max hand |> printfn "high card is %A"
    List.min hand |> printfn "low card is %A"

// ================================================
// Les Active patterns
// ================================================

module ActivePatternExamples =

    // F# a un type particulier de pattern matching nommé "active patterns"
    // où le pattern peut être parsé ou détecté dynamiquement.

    // "banana clips" est la syntaxe pour l'active patterns

    // par exemple, on définit un "active" pattern pour correspondre à des types "character"...
    let (|Digit|Letter|Whitespace|Other|) ch =
       if System.Char.IsDigit(ch) then Digit
       else if System.Char.IsLetter(ch) then Letter
       else if System.Char.IsWhiteSpace(ch) then Whitespace
       else Other

    // ... et ensuite on l'utilise pour rendre la logique de parsing plus claire
    let printChar ch =
      match ch with
      | Digit -> printfn "%c is a Digit" ch
      | Letter -> printfn "%c is a Letter" ch
      | Whitespace -> printfn "%c is a Whitespace" ch
      | _ -> printfn "%c is something else" ch

    // afficher une liste
    ['a';'b';'1';' ';'-';'c'] |> List.iter printChar

    // -----------------------------------------
    // FizzBuzz en utilisant les active patterns
    // -----------------------------------------

    // Vous pouvez créer un partial matching patterns également
    // On utilise just un underscore dans la définition, et on retourne Some si ça correspond.
    let (|MultOf3|_|) i = if i % 3 = 0 then Some MultOf3 else None
    let (|MultOf5|_|) i = if i % 5 = 0 then Some MultOf5 else None

    // la fonction principale
    let fizzBuzz i =
      match i with
      | MultOf3 & MultOf5 -> printf "FizzBuzz, "
      | MultOf3 -> printf "Fizz, "
      | MultOf5 -> printf "Buzz, "
      | _ -> printf "%i, " i

    // test
    [1..20] |> List.iter fizzBuzz

// ================================================
// Concision
// ================================================

module AlgorithmExamples =

    // F# a un haut ratio signal/bruit, permettant au code de se lire
    // presque comme un véritable algorithme

    // ------ Exemple: definir une fonction sumOfSquares ------
    let sumOfSquares n =
       [1..n]              // 1) Prendre tous les nombres de 1 à n
       |> List.map square  // 2) Elever chacun d'entre eux au carré
       |> List.sum         // 3) Effectuer leur somme

    // test
    sumOfSquares 100 |> printfn "Sum of squares = %A"

    // ------ Exemple: definir un fonction de tri ------
    let rec sort list =
       match list with
       // Si la liste est vide
       | [] ->
            []                            // on retourne une liste vide
       // si la list n'est pas vide
       | firstElem::otherElements ->      // on prend le premier élément
            let smallerElements =         // on extrait les éléments plus petits
                otherElements             // on prend les restants
                |> List.filter (fun e -> e < firstElem)
                |> sort                   // et on les trie
            let largerElements =          // on extrait les plus grands
                otherElements             // de ceux qui restent
                |> List.filter (fun e -> e >= firstElem)
                |> sort                   // et on les trie
            // On combine les 3 morceaux dans une nouvelle liste que l'on retourne
            List.concat [smallerElements; [firstElem]; largerElements]

    // test
    sort [1;5;23;18;9;1;3] |> printfn "Sorted = %A"

// ================================================
// Code Asynchrone
// ================================================

module AsyncExample =

    // F# inclus des fonctionnalités pour aider avec le code asynchrone
    // sans rencontrer la "pyramid of doom"
    //
    // L'exemple suivant télécharge une séquence de page web en parallèle.

    open System.Net
    open System
    open System.IO
    open Microsoft.FSharp.Control.CommonExtensions

    // Récupérer le contenu d'une URL de manière asynchrone
    let fetchUrlAsync url =
        async {   // Le mot clé "async" et les accolades
                  // créent un objet "asynchrone"
            let req = WebRequest.Create(Uri(url))
            use! resp = req.AsyncGetResponse()
                // use! est un assignement asynchrone
            use stream = resp.GetResponseStream()
                // "use" déclenche automatiquement close()
                // sur les ressources à la fin du scope
            use reader = new IO.StreamReader(stream)
            let html = reader.ReadToEnd()
            printfn "finished downloading %s" url
            }

    // une liste des sites à rapporter
    let sites = ["http://www.bing.com";
                 "http://www.google.com";
                 "http://www.microsoft.com";
                 "http://www.amazon.com";
                 "http://www.yahoo.com"]

    // C'est parti!
    sites
    |> List.map fetchUrlAsync  // créez une liste de tâche asynchrone
    |> Async.Parallel          // dites aux tâches de tourner en parallèle
    |> Async.RunSynchronously  // démarrez les!

// ================================================
// .NET compatabilité
// ================================================

module NetCompatibilityExamples =

    // F# peut réaliser presque tout ce que C# peut faire, et il s'intègre
    // parfaitement avec les librairies .NET ou Mono.

    // ------- Travaillez avec les fonctions des librairies existantes  -------

    let (i1success,i1) = System.Int32.TryParse("123");
    if i1success then printfn "parsed as %i" i1 else printfn "parse failed"

    // ------- Implémentez des interfaces à la volée! -------

    // Créer un nouvel objet qui implémente IDisposable
    let makeResource name =
       { new System.IDisposable
         with member this.Dispose() = printfn "%s disposed" name }

    let useAndDisposeResources =
        use r1 = makeResource "first resource"
        printfn "using first resource"
        for i in [1..3] do
            let resourceName = sprintf "\tinner resource %d" i
            use temp = makeResource resourceName
            printfn "\tdo something with %s" resourceName
        use r2 = makeResource "second resource"
        printfn "using second resource"
        printfn "done."

    // ------- Code orienté objet -------

    // F# est aussi un véritable language OO.
    // Il supporte les classes, l'héritage, les méthodes virtuelles, etc.

    // interface avec type générique
    type IEnumerator<'a> =
        abstract member Current : 'a
        abstract MoveNext : unit -> bool

    // Classe de base abstraite avec méthodes virtuelles
    [<AbstractClass>]
    type Shape() =
        // propriétés en lecture seule
        abstract member Width : int with get
        abstract member Height : int with get
        // méthode non-virtuelle
        member this.BoundingArea = this.Height * this.Width
        // méthode virtuelle avec implémentation de la classe de base
        abstract member Print : unit -> unit
        default this.Print () = printfn "I'm a shape"

    // classe concrète qui hérite de sa classe de base et surcharge
    type Rectangle(x:int, y:int) =
        inherit Shape()
        override this.Width = x
        override this.Height = y
        override this.Print ()  = printfn "I'm a Rectangle"

    // test
    let r = Rectangle(2,3)
    printfn "The width is %i" r.Width
    printfn "The area is %i" r.BoundingArea
    r.Print()

    // ------- extension de méthode  -------

    // Juste comme en C#, F# peut étendre des classes existantes avec des extensions de méthode.
    type System.String with
       member this.StartsWithA = this.StartsWith "A"

    // test
    let s = "Alice"
    printfn "'%s' starts with an 'A' = %A" s s.StartsWithA

    // ------- événements -------

    type MyButton() =
        let clickEvent = new Event<_>()

        [<CLIEvent>]
        member this.OnClick = clickEvent.Publish

        member this.TestEvent(arg) =
            clickEvent.Trigger(this, arg)

    // test
    let myButton = new MyButton()
    myButton.OnClick.Add(fun (sender, arg) ->
            printfn "Click event with arg=%O" arg)

    myButton.TestEvent("Hello World!")

Plus d'information

Pour plus de démonstration de F#, rendez-vous sur le site Try F#, ou suivez la série why use F#.

Apprenez en davantage à propose de F# sur fsharp.org.


Vous avez une suggestion ? Peut-être une correction ? Ouvrez un ticket sur GitHub, ou faites vous-même une pull request !

Version originale par Scott Wlaschin, mis à jour par 2 contributeur(s).