// 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 [] 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<_>() [] 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!")