Nouveautés de F# 4.5

F# 4.5 ajoute plusieurs améliorations au langage F#. La plupart de ces fonctionnalités ont été ajoutées ensemble pour vous permettre d’écrire du code efficace en F# tout en garantissant la sécurité de ce code. Cela signifie ajouter quelques concepts au langage et une quantité importante d’analyse du compilateur lors de l’utilisation de ces constructions.

Bien démarrer

F# 4.5 est disponible dans toutes les distributions .NET Core et les outils Visual Studio. Consultez Prise en main avec F# pour en savoir plus.

Structs span et byref

Le type Span<T> introduit dans .NET Core vous permet de représenter les mémoires tampons en mémoire de manière fortement typée, ce qui est désormais autorisé en F# à partir de F# 4.5. L’exemple suivant montre comment réutiliser une fonction fonctionnant sur un Span<T> avec différentes représentations de mémoire tampon :

let safeSum (bytes: Span<byte>) =
    let mutable sum = 0
    for i in 0 .. bytes.Length - 1 do
        sum <- sum + int bytes[i]
    sum

// managed memory
let arrayMemory = Array.zeroCreate<byte>(100)
let arraySpan = new Span<byte>(arrayMemory)

safeSum(arraySpan) |> printfn "res = %d"

// native memory
let nativeMemory = Marshal.AllocHGlobal(100);
let nativeSpan = new Span<byte>(nativeMemory.ToPointer(), 100)

safeSum(nativeSpan) |> printfn "res = %d"
Marshal.FreeHGlobal(nativeMemory)

// stack memory
let mem = NativePtr.stackalloc<byte>(100)
let mem2 = mem |> NativePtr.toVoidPtr
let stackSpan = Span<byte>(mem2, 100)

safeSum(stackSpan) |> printfn "res = %d"

Un aspect important à cela est que Span et d’autres structs de type byref ont une analyse statique très rigide effectuée par le compilateur qui limite leur utilisation d’une manière inattendue. Il s’agit du compromis fondamental entre les performances, l’expressivité et la sécurité qui est introduit dans F# 4.5.

Amélioration des byrefs

Avant F# 4.5, les byrefs en F# étaient dangereux et non sécurisés pour de nombreuses applications. Les problèmes de solidité autour des byrefs ont été traités dans F# 4.5 et la même analyse statique effectuée pour les structs de type span et byref a également été appliquée.

inref<’T> et outref<’T>

Pour représenter la notion de pointeur managé en lecture seule, en écriture seule et en lecture/écriture, F# 4.5 introduit les types inref<'T>, outref<'T> pour représenter des pointeurs en lecture seule et en écriture seule, respectivement. Chacun d’eux a une sémantique différente. Par exemple, vous ne pouvez pas écrire dans un inref<'T> :

let f (dt: inref<DateTime>) =
    dt <- DateTime.Now // ERROR - cannot write to an inref!

Par défaut, l’inférence de type déduit les pointeurs managés comme inref<'T> étant conformes à la nature immuable du code F#, sauf si quelque chose a déjà été déclaré comme mutable. Pour rendre quelque chose accessible en écriture, vous devez déclarer un type comme mutable avant de transmettre son adresse à une fonction ou à un membre qui le manipule. Pour en savoir plus, consultez Byrefs.

Structs en lecture seule

À partir de F# 4.5, vous pouvez annoter un struct avec IsReadOnlyAttribute en tant que tel :

[<IsReadOnly; Struct>]
type S(count1: int, count2: int) =
    member x.Count1 = count1
    member x.Count2 = count2

Cela vous interdit de déclarer un membre mutable dans le struct et émet des métadonnées qui permettent à F# et C# de le traiter en lecture seule lorsqu’il est consommé à partir d’un assembly. Pour plus d’informations, consultez Structs en lecture seule.

Pointeurs vides

Le type voidptr est ajouté à F# 4.5, tout comme les fonctions suivantes :

  • NativePtr.ofVoidPtr pour convertir un pointeur vide en pointeur int natif
  • NativePtr.toVoidPtr pour convertir un pointeur int natif en pointeur vide

Cela est utile lors de l’interopérabilité avec un composant natif qui utilise des pointeurs vides.

Le mot clé match!

Le mot clé match! améliore la correspondance de modèle à l’intérieur d’une expression de calcul :

// Code that returns an asynchronous option
let checkBananaAsync (s: string) =
    async {
        if s = "banana" then
            return Some s
        else
            return None
    }

// Now you can use 'match!'
let funcWithString (s: string) =
    async {
        match! checkBananaAsync s with
        | Some bananaString -> printfn "It's banana!"
        | None -> printfn "%s" s
}

Cela vous permet de raccourcir le code qui implique souvent la combinaison d’options (ou d’autres types) avec des expressions de calcul telles qu’async. Pour en savoir plus, consultez match!.

Exigences pour un upcast souples dans les expressions de tableau, de liste et de séquence

La combinaison de types où l’un peut hériter d’un autre à l’intérieur d’expressions de tableau, de liste et de séquence a traditionnellement exigé que vous effectuiez un upcast de n’importe quel type dérivé vers son type parent avec :> ou upcast. Ceci est maintenant plus simple, comme montré ici :

let x0 : obj list  = [ "a" ] // ok pre-F# 4.5
let x1 : obj list  = [ "a"; "b" ] // ok pre-F# 4.5
let x2 : obj list  = [ yield "a" :> obj ] // ok pre-F# 4.5

let x3 : obj list  = [ yield "a" ] // Now ok for F# 4.5, and can replace x2

Relaxation de la mise en retrait pour les expressions de tableau et de liste

Avant F# 4.5, vous deviez mettre en retrait excessivement les expressions de tableau et de liste lorsqu’elles sont passées en tant qu’arguments aux appels de méthode. Cela n’est plus requis :

module NoExcessiveIndenting =
    System.Console.WriteLine(format="{0}", arg = [|
        "hello"
    |])
    System.Console.WriteLine([|
        "hello"
    |])