Blog Arolla

Inspiration fonctionnelle – Irrelevantable, partie 2

Tentons d’exprimer quelque chose de plus que Nullable<T>

Pas de soirée Craftsmanship hier soir pour moi… un peu déçu, mais au moins j’en ai profité pour avancer sur la mise en ligne de ce billet !

Il s’agit du troisième billet d’une série sur la façon dont la programmation fonctionnelle, et la formation F# à laquelle j’ai récemment assisté, m’apportent de l’inspiration dans mon travail quotidien en C#. Ce post présentera une implémentation en F# du type Irrelevantable précédemment construit en C#.

Passons directement au code, voici la structure :

type Irrelevantable<'a> =
    | Relevant of 'a
    | Absent
    | Irrelevant

En utilisant un simple “discriminated union type”, on peut construire sans artifice une structure gérant les trois cas nécessaires.

Voyons maintenant comment ce type peut être utilisé. Je commence par définir un type Product et une liste de produits :

type Product =
  { Name: string;
    ProductType: string;
    Strike: Irrelevantable<decimal>;
    Premium: Irrelevantable<decimal>;
    TradedPrice: Irrelevantable<decimal>
    SwapPrice: Irrelevantable<decimal>; }

let products =
  [ { Name = "568745";
      ProductType = "Option";
      Strike = Relevant(176m);
      Premium = Relevant(3.72m);
      TradedPrice = Irrelevant;
      SwapPrice = Irrelevant } ;
    { Name = "568746";
      ProductType = "Option";
      Strike = Relevant(176m);
      Premium = Absent;
      TradedPrice = Irrelevant;
      SwapPrice = Irrelevant } ;
    { Name = "568747";
      ProductType = "Swap  ";
      Strike = Irrelevant;
      Premium = Irrelevant;
      TradedPrice = Relevant(15.3m);
      SwapPrice = Relevant(15.6m) } ]

L’objectif est maintenant d’afficher un rapport basé sur ces produits. Je définis pour cela quelques fonctions utilitaires et/ou de formatage :

let formatValue formatter value =
    match value with
    | Relevant(data) -> (formatter data)
    | Absent -> ""
    | Irrelevant -> "-"

let padLeft (value:string) =
    value.PadLeft(7)

let formatPrice =
    formatValue (fun (d:decimal) -> d.ToString("N2"))
    
let getLine vals =
    System.String.Join(" | ", vals |> Seq.map padLeft)

let getValues p = 
    p.Name ::
    p.ProductType ::
    (formatPrice p.Strike) ::
    (formatPrice p.Premium) ::
    (formatPrice p.TradedPrice) ::
    (formatPrice p.SwapPrice) :: []

let getProductLine =
    getValues >> getLine

On peut remarque ici qu’en jouant avec la syntaxe F#, en plus d’utiliser l’opérateur de pipe ( |> ), j’ai également utilisé une composition de fonctions, grâce à l’opérateur >> (qui n’est pas une redirection de flux C++ !).

Et j’utilise ces fonctions pour générer les lignes à afficher, en commençant par une ligne d’en-tête :

let headerLine =
  ( "Ref." ::
    "Type" ::
    "Strike" ::
    "Premium" ::
    "Price" ::
    "Swap price" :: [])
  |> getLine

let productLines =
  products |> List.map getProductLine

let allLines =
    headerLine :: productLines

Finalement, je peux afficher le rapport :

let test = allLines |> List.iter (printfn "%s")

Et l’affichage en sortie est le suivant :

   Ref. |    Type |  Strike | Premium |   Price | Swap price
 568745 |  Option |  176,00 |    3,72 |       - |       -
 568746 |  Option |  176,00 |         |       - |       -
 568747 |  Swap   |       - |       - |   15,30 |   15,60

On peut alors remarquer la différence entre une valeur absente (dans la colonne Premium pour le deuxième produit), et les valeurs qui n’ont aucun sens, et qui sont représentées par un tiret.

@pirrmann

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *