Novedades de F# 4.5

F# 4.5 agrega varias mejoras al lenguaje F#. Muchas de estas características se han agregado conjuntamente para permitirle escribir código eficaz en F# y, al mismo tiempo, garantizar que este código sea seguro. Esto conlleva agregar algunos conceptos al lenguaje y una cantidad considerable de análisis del compilador al usar estas construcciones.

Introducción

F# 4.5 está disponible en todas las distribuciones de .NET Core y las herramientas de Visual Studio. Introducción a F# para obtener más información.

Estructuras de tipo Span y byref

El Span<T> tipo que se introdujo en .NET Core permite representar búferes en memoria de una manera fuertemente tipada, lo que ahora se permite en F# desde F# 4.5. En el ejemplo siguiente se muestra cómo se puede volver a usar una función que opera en Span<T> con representaciones de búfer diferentes:

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 aspecto importante de esto es que Span y otras estructuras de tipo byref tienen análisis estáticos muy rígidos realizados por el compilador que restringen su uso de formas que podrían ser inesperadas. Este es el principal inconveniente en la correlación entre el rendimiento, la expresividad y la seguridad que se ha introducido en F# 4.5.

Renovación de las estructuras byref

Antes de F# 4.5, las estructuras byrefs de F# no eran seguras ni estables para numerosas aplicaciones. En F# 4.5, se han resuelto los problemas relacionados con la estabilidad de las estructuras byref y también se ha aplicado el mismo análisis estático que se realizaba para las estructuras de tipo Span y byref.

inref<'T> y outref<'T>

Para representar el concepto de puntero administrado de solo lectura, solo escritura, y lectura y escritura, se han introducido en F# 4.5 los tipos inref<'T> y outref<'T> para representar los punteros de solo lectura y de solo escritura, respectivamente. Cada uno tiene una semántica diferente. Por ejemplo, no puede escribir en inref<'T>:

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

De forma predeterminada, la inferencia de tipos inferirá los punteros administrados como inref<'T> para ajustarse a la naturaleza inmutable del código de F#, a menos que ya se haya declarado algo como mutable. Para que se pueda escribir algo, deberá declarar un tipo como mutable antes de pasar su dirección a una función o miembro que lo manipule. Para obtener más información, consulte Estructuras byref.

Estructuras readonly

A partir de F# 4.5, se puede anotar una estructura con IsReadOnlyAttribute como tal:

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

Esto impide declarar un miembro mutable en la estructura y emite metadatos que permiten a F# y C# tratarlo como de solo lectura cuando se consume desde un ensamblado. Para obtener más información, consulte Estructuras ReadOnly.

Punteros void

Se ha agregado el tipo voidptr a F# 4.5, como en las funciones siguientes:

  • NativePtr.ofVoidPtr para convertir un puntero void en un puntero native int.
  • NativePtr.toVoidPtr para convertir un puntero native int en un puntero void.

Esto resulta útil al interoperar con un componente nativo que usa punteros void.

La palabra clave match!.

La palabra clave match! mejora la coincidencia de patrones cuando se encuentra dentro de una expresión de cálculo:

// 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
}

Esto permite acortar código que suele implicar la combinación de opciones (u otros tipos) con expresiones de cálculo como async. Consulte match! para obtener más información.

Requisitos flexibles de conversión a tipo básico en expresiones de matriz, lista y secuencia

Tradicionalmente, la combinación de tipos en la que uno puede heredar de otro dentro de expresiones de matriz, lista y secuencia requería convertir un tipo derivado a su tipo primario con :> o upcast. Esto se ha flexibilizado, como se muestra a continuación:

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

Flexibilización de la sangría en expresiones de matriz y lista

Antes de F# 4.5, era necesario aplicar una sangría excesiva a las expresiones de matriz y lista cuando se pasaban como argumentos a llamadas de método. Esto ya no es necesario:

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