Fonctions : compléments
Last updated
Was this helpful?
Last updated
Was this helpful?
💡 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)
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 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
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)
Définies avec mot-clé member
plutôt que let
Choix du self-identifier : this
, me
, _
...
Paramètres sont au choix :
Tuplifiés : style OOP
Curryfiés : style FP
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`
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
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()
BCL = Base Class Library .NET
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) {}
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)
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 📍
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.");
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>
📍
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()
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`
Les appels ne marche pas avec un vrai tuple F♯ (3)