Fonctions : compléments
Mémoïsation
💡 Idée : réduire le temps de calcul d'une fonction
❓ Comment : mise en cache des résultats → Au prochain appel avec mêmes arguments, renverra résultat en cache
👉 En pratique : fonction memoizeN
de la librairie FSharpPlus
⚠️ Attention : Comme tout optimisation, à utiliser quand le besoin se fait sentir et en validant (mesurant) que cela marche sans désagrément annexe.
☝ Ne pas confondre avec expression lazy
(slide suivante)
Lazy expression
Sucre syntaxique pour créer un objet .NET Lazy<'T>
à partir d'une expression
→ Expression pas évaluée immédiatement mais qu'à la 1ère demande (Thunk)
→ Intéressant pour améliorer performance sans trop complexifier le code
let printAndForward x = printfn $"{x}"; x
let a = lazy (printAndForward "a")
let b = printAndForward "b"
// > b
printfn $"{a.Value} et {b}"
// > a
// > a et b
printfn $"{a.Value} et c"
// > a et c
Lazy
active pattern
Lazy
active patternExtrait la valeur dans un objet Lazy
let triple (Lazy i) = 3 * i // Lazy<int> -> int
let v =
lazy
(printf "eval!"
5 + 5) // Lazy<int>
triple v
// eval!val it: int = 30
triple v
// val it: int = 30
Organisation des fonctions
3 façons d'organiser les fonctions = 3 endroits où les déclarer :
Module : fonction déclarée dans un module 📍 Module
Nested : fonction déclarée à l'intérieur d'une valeur / fonction
💡 Encapsuler des helpers utilisés juste localement
☝ Paramètres de la fonction chapeau accessibles à fonction nested
Method : fonction définie comme méthode dans un type (next slide)
Méthodes
Définies avec mot-clé
member
plutôt quelet
Choix du self-identifier :
this
,me
,_
...Paramètres sont au choix :
Tuplifiés : style OOP
Curryfiés : style FP
Méthodes - Exemple
type Product = { SKU: string; Price: float } with // 👈 `with` nécessaire pour l'indentation
// Style avec tuplification et `this` // Alternative : `{ SKU...}` à la ligne
member this.TupleTotal(qty, discount) =
(this.Price * float qty) - discount
// Style avec curryfication et `me`
member me.CurriedTotal qty discount = // 👈 `me` désigne le "this"
(me.Price * float qty) - discount // 👈 `me.Price` pour accéder à la propriété `Price`
Fonction vs Méthode
Nommage
camelCase
PascalCase
Curryfication
✅ oui
✅ si ni tuplifiée, ni surchargée
Paramètres nommés
❌ non
✅ si tuplifiés
Paramètres optionnels
❌ non
✅ si tuplifiés
Surcharge / overload
❌ non
✅ si tuplifiés
Inférence des arguments → à la déclaration
➖ Possible
✅ oui, du this
➖ Possible pour les autres arguments
→ à l'usage
✅ oui
❌ non, il faut annoter le type de l'objet pour utiliser une de ses méthodes
En argument d'une high-order fn
✅ oui
❌ non, lambda nécessaire
Support du inline
✅ oui
✅ oui
Récursive
✅ si rec
✅ oui
How To: Pipeline avec une méthode d'instance ?
Exemple : comment "piper" la méthode ToLower()
d'une string
?
Via lambda :
"MyString" |> (fun x -> x.ToLower())
Idem via fonction nommée telle que :
String.toLower
de la librairie FSharpPlus"MyString" |> String.toLower
Alternative au pipeline : passer par une valeur intermédiaire :
→ let low = "MyString".ToLower()
Interop avec la BCL
BCL = Base Class Library .NET
Méthode void
Une méthode .NET void
est vue en F# comme renvoyant unit
.
let liste = System.Collections.Generic.List<int>()
liste.Add
(* IntelliSense Ionide:
abstract member Add:
item: int
-> unit
*)
Réciproquement, une fonction F# renvoyant unit
est compilée en une méthode void
.
// F#
let ignore _ = ()
Equivalent C# d'après SharpLab :
public static void ignore<a>(a _arg1) {}
Appel à une méthode de la BCL à N arguments
Une méthode .NET avec plusieurs arguments est "pseudo-tuplifiée" :
Les arguments doivent tous être spécifiés (1)
L'application partielle des paramètres n'est pas supportée (2)
Les appels ne marche pas avec un vrai tuple F♯ ⚠️ (3)
System.String.Compare("a", "b") // ✅ (1)
System.String.Compare ("a","b") // ✅
System.String.Compare "a" "b" // ❌ (2)
System.String.Compare "a","b" // ❌
let tuple = ("a","b")
System.String.Compare tuple // ❌ (3)
💡 Note : on peut utiliser l'opérateur pipe |>
pour appeler une méthode tuplifiée
→ Cf. Orienté-objet / Membres 📍
Paramètre out
- En C♯
out
- En C♯out
utilisé pour avoir plusieurs valeurs en sortie
→ Ex : Int32.TryParse
, Dictionary<,>.TryGetValue
:
if (int.TryParse(maybeInt, out var value))
Console.WriteLine($"It's the number {value}.");
else
Console.WriteLine($"{maybeInt} is not a number.");
Paramètre out
- En F♯
out
- En F♯Possibilité de consommer la sortie sous forme de tuple 👍
match System.Int32.TryParse maybeInt with
| true, i -> printf $"It's the number {value}."
| false, _ -> printf $"{maybeInt} is not a number."
💡 Fonctions F♯ tryXxx
s'appuient plutôt sur le type Option<T>
📍
Instancier une classe avec new
?
new
?// (1) `new` autorisé mais non recommandé
type MyClass(i) = class end
let c1 = MyClass(12) // 👍
let c2 = new MyClass(234) // 👌 mais pas idiomatique
// (2) IDisposable => `new` obligatoire et `use` plutôt que `let`
open System.IO
let fn () =
let _ = FileStream("hello.txt", FileMode.Open)
// ⚠️ Warning : Il est recommandé que les objets prenant en charge
// l'interface IDisposable soient créés avec la syntaxe 'new Type(args)'
use f = new FileStream("hello.txt", FileMode.Open)
f.Close()
Appel d'une méthode surchargée
Compilateur peut ne pas comprendre quelle surcharge est appelée
Astuce : faire appel avec argument nommé
let createReader fileName =
new System.IO.StreamReader(path=fileName)
// ☝️ Param `path` → `filename` inféré en `string`
let createReaderByStream stream =
new System.IO.StreamReader(stream=stream)
// ☝️ Param `stream` de type `System.IO.Stream`
Mis à jour
Ce contenu vous a-t-il été utile ?