Megosztás a következőn keresztül:


Az F# 5 újdonságai

Az F# 5 számos fejlesztést tartalmaz az F# nyelvhez és az F# Interactivehoz. A .NET 5-tel jelenik meg.

A legújabb .NET SDK-t a .NET letöltési oldaláról töltheti le.

Első lépések

Az F# 5 minden .NET Core-disztribúcióban és Visual Studio-eszközben elérhető. További információt az F# használatának első lépései című témakörben talál.

Csomaghivatkozások F#-szkriptekben

Az F# 5 támogatja a szintaxist tartalmazó #r "nuget:..." F#-szkriptekben található csomaghivatkozásokat. Vegyük például a következő csomaghivatkozást:

#r "nuget: Newtonsoft.Json"

open Newtonsoft.Json

let o = {| X = 2; Y = "Hello" |}

printfn $"{JsonConvert.SerializeObject o}"

A csomag neve után a következőhöz hasonló explicit verziót is megadhat:

#r "nuget: Newtonsoft.Json,11.0.1"

A csomaghivatkozások natív függőségekkel rendelkező csomagokat támogatnak, például ML.NET.

A csomaghivatkozások olyan csomagokat is támogatnak, amelyek speciális követelményeket támasztanak a függőkre .dllvaló hivatkozással kapcsolatban. Az FParsec-csomag például megköveteli, hogy a felhasználók manuálisan győződjenek meg arról, hogy a függőre FParsecCS.dll először hivatkoztak, mielőtt FParsec.dll az F# Interactiveban hivatkoztak rá. Erre már nincs szükség, és az alábbiak szerint hivatkozhat a csomagra:

#r "nuget: FParsec"

open FParsec

let test p str =
    match run p str with
    | Success(result, _, _)   -> printfn $"Success: {result}"
    | Failure(errorMsg, _, _) -> printfn $"Failure: {errorMsg}"

test pfloat "1.234"

Ez a funkció implementálja az F# Tooling RFC FST-1027-et. A csomaghivatkozásokról további információt az F# interaktív oktatóanyagában talál.

Sztring interpolációja

Az F# interpolált sztringek meglehetősen hasonlítanak a C# vagy JavaScript interpolált sztringekhez, mivel lehetővé teszik a sztringkonstanson belüli "lyukakba" írt kódot. Íme egy egyszerű példa:

let name = "Phillip"
let age = 29
printfn $"Name: {name}, Age: {age}"

printfn $"I think {3.0 + 0.14} is close to {System.Math.PI}!"

Az F# interpolált sztringek azonban a függvényhez hasonlóan lehetővé teszik a sprintf beírt interpolációk használatát is annak kikényszerítéséhez, hogy egy interpolált kontextusban lévő kifejezés megfeleljen egy adott típusnak. Ugyanazokat a formátumkijelölőket használja.

let name = "Phillip"
let age = 29

printfn $"Name: %s{name}, Age: %d{age}"

// Error: type mismatch
printfn $"Name: %s{age}, Age: %d{name}"

Az előző beírt interpolációs példában az %s interpoláció típusának stringkell lennie , míg az %d interpolációnak integeregy .

Emellett tetszőleges F#-kifejezés (vagy kifejezés) elhelyezhető az interpolációs környezet oldalán. Még egy bonyolultabb kifejezést is meg lehet írni, például:

let str =
    $"""The result of squaring each odd item in {[1..10]} is:
{
    let square x = x * x
    let isOdd x = x % 2 <> 0
    let oddSquares xs =
        xs
        |> List.filter isOdd
        |> List.map square
    oddSquares [1..10]
}
"""

Bár nem javasoljuk, hogy ezt túl sokat tegye a gyakorlatban.

Ez a funkció implementálja az F# RFC FS-1001-et.

A névtámogatás

Az F# 5 támogatja az nameof operátort, amely feloldja a használt szimbólumot, és létrehozza a nevét F# forrásban. Ez különböző helyzetekben hasznos, például naplózás esetén, és védelmet nyújt a naplózásnak a forráskód változásaival szemben.

let months =
    [
        "January"; "February"; "March"; "April";
        "May"; "June"; "July"; "August"; "September";
        "October"; "November"; "December"
    ]

let lookupMonth month =
    if (month > 12 || month < 1) then
        invalidArg (nameof month) (sprintf "Value passed in was %d." month)

    months[month-1]

printfn $"{lookupMonth 12}"
printfn $"{lookupMonth 1}"
printfn $"{lookupMonth 13}"

Az utolsó sor kivételt eredményez, és a "hónap" megjelenik a hibaüzenetben.

Szinte minden F#-szerkezet nevét felveheti:

module M =
    let f x = nameof x

printfn $"{M.f 12}"
printfn $"{nameof M}"
printfn $"{nameof M.f}"

Három utolsó kiegészítés az operátorok működésének módosítása: az űrlap hozzáadása az nameof<'type-parameter> általános típusparaméterekhez, valamint a mintaegyeztetési kifejezés mintaként való használatának nameof képessége.

Egy operátor nevének megadása megadja a forrássztringet. Ha a lefordított űrlapra van szüksége, használja az operátor lefordított nevét:

nameof(+) // "+"
nameof op_Addition // "op_Addition"

Egy típusparaméter nevének megadása kissé eltérő szintaxist igényel:

type C<'TType> =
    member _.TypeName = nameof<'TType>

Ez hasonló az és typedefof<'T> az typeof<'T> operátorokhoz.

Az F# 5 emellett támogatja a nameof kifejezésekben match használható mintákat:

[<Struct; IsByRefLike>]
type RecordedEvent = { EventType: string; Data: ReadOnlySpan<byte> }

type MyEvent =
    | AData of int
    | BData of string

let deserialize (e: RecordedEvent) : MyEvent =
    match e.EventType with
    | nameof AData -> AData (JsonSerializer.Deserialize<int> e.Data)
    | nameof BData -> BData (JsonSerializer.Deserialize<string> e.Data)
    | t -> failwithf "Invalid EventType: %s" t

Az előző kód a "nameof" kifejezést használja a sztringkonstans helyett az egyezés kifejezésben.

Ez a funkció az F# RFC FS-1003-at implementálja.

Típusdeklarációk megnyitása

Az F# 5 támogatja a nyílt típusú deklarációkat is. A nyitott típusú deklaráció olyan, mint egy statikus osztály megnyitása A C#-ban, kivéve néhány különböző szintaxist és kissé eltérő viselkedést az F# szemantikájának megfelelően.

Nyitott típusú deklarációkkal bármilyen típussal közzéteheti open a benne lévő statikus tartalmakat. Emellett F#-ként definiált egyesítőket és rekordokat is használhat open a tartalom nyilvánosságra hozásához. Ez például akkor lehet hasznos, ha egy modulban egy egyesítés van definiálva, és hozzá szeretne férni az esetekhez, de nem szeretné megnyitni a teljes modult.

open type System.Math

let x = Min(1.0, 2.0)

module M =
    type DU = A | B | C

    let someOtherFunction x = x + 1

// Open only the type inside the module
open type M.DU

printfn $"{A}"

A C#-tól eltérően, open type ha két olyan típuson dolgozik, amelyek egy azonos nevű tagot fednek fel, az utoljára használt tag openárnyékot ad a másik névnek. Ez összhangban van a már meglévő árnyékolásT körülvevő F#-szemantikával.

Ez a funkció az F# RFC FS-1068-at implementálja.

Konzisztens szeletelési viselkedés a beépített adattípusokhoz

A beépített FSharp.Core adattípusok (tömb, lista, sztring, 2D tömb, 3D tömb, 4D tömb) szeletelésének viselkedése, amely az F# 5 előtt nem konzisztens. Néhány peremes eset viselkedése kivételt eredményezett, mások pedig nem. Az F# 5-ben az összes beépített típus üres szeleteket ad vissza olyan szeletekhez, amelyeket lehetetlen létrehozni:

let l = [ 1..10 ]
let a = [| 1..10 |]
let s = "hello!"

// Before: would return empty list
// F# 5: same
let emptyList = l[-2..(-1)]

// Before: would throw exception
// F# 5: returns empty array
let emptyArray = a[-2..(-1)]

// Before: would throw exception
// F# 5: returns empty string
let emptyString = s[-2..(-1)]

Ez a funkció az F# RFC FS-1077-et implementálja.

Rögzített indexszeletek 3D és 4D tömbökhöz az FSharp.Core-ban

Az F# 5 támogatja a szeletelést a beépített 3D és 4D tömbtípusok rögzített indexével.

Ennek szemléltetéséhez vegye figyelembe a következő 3D tömböt:

z = 0

x\y 0 0
0 0 0
1 2 3

z = 1

x\y 0 0
0 4 5
1 6 7

Mi a teendő, ha ki szeretné nyerni a szeletet [| 4; 5 |] a tömbből? Ez most nagyon egyszerű!

// First, create a 3D array to slice

let dim = 2
let m = Array3D.zeroCreate<int> dim dim dim

let mutable count = 0

for z in 0..dim-1 do
    for y in 0..dim-1 do
        for x in 0..dim-1 do
            m[x,y,z] <- count
            count <- count + 1

// Now let's get the [4;5] slice!
m[*, 0, 1]

Ez a funkció az F# RFC FS-1077b verziót implementálja.

Az F#-idézetek fejlesztései

Az F# -kód idézőjelei mostantól megőrizhetik a típusmegkötési információkat. Vegyük a következő példát:

open FSharp.Linq.RuntimeHelpers

let eval q = LeafExpressionConverter.EvaluateQuotation q

let inline negate x = -x
// val inline negate: x: ^a ->  ^a when  ^a : (static member ( ~- ) :  ^a ->  ^a)

<@ negate 1.0 @>  |> eval

A függvény által inline létrehozott kényszer megmarad a kód idézőjelében. A negate függvény idézett űrlapja mostantól kiértékelhető.

Ez a funkció az F# RFC FS-1071-et implementálja.

Alkalmazásalapú számítási kifejezések

A számítási kifejezések (CE-k) ma a "környezetfüggő számítások" modellezésére szolgálnak, vagy funkcionálisabb, programozási barát terminológiában, a monadikus számításokban.

Az F# 5 alkalmazásalapú CE-ket vezet be, amelyek egy másik számítási modellt kínálnak. Az applicative CEs lehetővé teszi a hatékonyabb számítások elvégzését, feltéve, hogy minden számítás független, és az eredmények a végén halmozódnak fel. Ha a számítások egymástól függetlenek, akkor is triviálisan párhuzamosak, így a CE-szerzők hatékonyabb kódtárakat írhatnak. Ez az előny azonban korlátozással jár: a korábban kiszámított értékektől függő számítások nem engedélyezettek.

Az alábbi példa egy egyszerű, a típushoz használható Result CE-t mutat be.

// First, define a 'zip' function
module Result =
    let zip x1 x2 =
        match x1,x2 with
        | Ok x1res, Ok x2res -> Ok (x1res, x2res)
        | Error e, _ -> Error e
        | _, Error e -> Error e

// Next, define a builder with 'MergeSources' and 'BindReturn'
type ResultBuilder() =
    member _.MergeSources(t1: Result<'T,'U>, t2: Result<'T1,'U>) = Result.zip t1 t2
    member _.BindReturn(x: Result<'T,'U>, f) = Result.map f x

let result = ResultBuilder()

let run r1 r2 r3 =
    // And here is our applicative!
    let res1: Result<int, string> =
        result {
            let! a = r1
            and! b = r2
            and! c = r3
            return a + b - c
        }

    match res1 with
    | Ok x -> printfn $"{nameof res1} is: %d{x}"
    | Error e -> printfn $"{nameof res1} is: {e}"

let printApplicatives () =
    let r1 = Ok 2
    let r2 = Ok 3 // Error "fail!"
    let r3 = Ok 4

    run r1 r2 r3
    run r1 (Error "failure!") r3

Ha Ön olyan könyvtárszerző, aki ma teszi elérhetővé a CES-eket a tárukban, néhány további szempontot is figyelembe kell vennie.

Ez a funkció az F# RFC FS-1063-at implementálja.

A felületek különböző általános példányokban implementálhatók

Most már implementálhatja ugyanazt a felületet különböző általános példányokon:

type IA<'T> =
    abstract member Get : unit -> 'T

type MyClass() =
    interface IA<int> with
        member x.Get() = 1
    interface IA<string> with
        member x.Get() = "hello"

let mc = MyClass()
let iaInt = mc :> IA<int>
let iaString = mc :> IA<string>

iaInt.Get() // 1
iaString.Get() // "hello"

Ez a funkció az F# RFC FS-1031-et implementálja.

Alapértelmezett felülettag-használat

Az F# 5 lehetővé teszi az alapértelmezett implementációkkal rendelkező felületek használatát.

Fontolja meg a C#-ban definiált felületet a következő módon:

using System;

namespace CSharp
{
    public interface MyDim
    {
        public int Z => 0;
    }
}

Az interfész implementálásának bármely szabványos eszközén keresztül használhatja az F#-ban:

open CSharp

// You can implement the interface via a class
type MyType() =
    member _.M() = ()

    interface MyDim

let md = MyType() :> MyDim
printfn $"DIM from C#: %d{md.Z}"

// You can also implement it via an object expression
let md' = { new MyDim }
printfn $"DIM from C# but via Object Expression: %d{md'.Z}"

Így biztonságosan kihasználhatja a modern C# nyelven írt C#-kód és .NET-összetevők előnyeit, amikor elvárják, hogy a felhasználók használhassák az alapértelmezett implementációt.

Ez a funkció az F# RFC FS-1074-et implementálja.

Egyszerűsített interop null értékű típusokkal

A null értékű (korábbi nevén Nullable Types) típusokat már régóta támogatja az F#, de a velük való interakció hagyományosan némi fájdalommal jár, mivel minden alkalommal létre kell hoznunk egy Nullable vagy Nullable<SomeType> több burkolót, amikor értéket szeretnénk átadni. Most a fordító implicit módon átalakít egy értéktípust egy Nullable<ThatValueType> olyan értékgé, amely megfelel a céltípusnak. A következő kód már lehetséges:

#r "nuget: Microsoft.Data.Analysis"

open Microsoft.Data.Analysis

let dateTimes = PrimitiveDataFrameColumn<DateTime>("DateTimes")

// The following line used to fail to compile
dateTimes.Append(DateTime.Parse("2019/01/01"))

// The previous line is now equivalent to this line
dateTimes.Append(Nullable<DateTime>(DateTime.Parse("2019/01/01")))

Ez a funkció implementálja az F# RFC FS-1075-öt.

Előzetes verzió: fordított indexek

Az F# 5 egy előzetes verziót is bevezet a fordított indexek engedélyezéséhez. A szintaxis a következő: ^idx. Az 1. elem értéke a lista végéről a következőképpen érhető el:

let xs = [1..10]

// Get element 1 from the end:
xs[^1]

// From the end slices

let lastTwoOldStyle = xs[(xs.Length-2)..]

let lastTwoNewStyle = xs[^1..]

lastTwoOldStyle = lastTwoNewStyle // true

A fordított indexeket a saját típusaihoz is definiálhatja. Ehhez a következő módszert kell implementálnia:

GetReverseIndex: dimension: int -> offset: int

Íme egy példa a Span<'T> típusra:

open System

type Span<'T> with
    member sp.GetSlice(startIdx, endIdx) =
        let s = defaultArg startIdx 0
        let e = defaultArg endIdx sp.Length
        sp.Slice(s, e - s)

    member sp.GetReverseIndex(_, offset: int) =
        sp.Length - offset

let printSpan (sp: Span<int>) =
    let arr = sp.ToArray()
    printfn $"{arr}"

let run () =
    let sp = [| 1; 2; 3; 4; 5 |].AsSpan()

    // Pre-# 5.0 slicing on a Span<'T>
    printSpan sp[0..] // [|1; 2; 3; 4; 5|]
    printSpan sp[..3] // [|1; 2; 3|]
    printSpan sp[1..3] // |2; 3|]

    // Same slices, but only using from-the-end index
    printSpan sp[..^0] // [|1; 2; 3; 4; 5|]
    printSpan sp[..^2] // [|1; 2; 3|]
    printSpan sp[^4..^2] // [|2; 3|]

run() // Prints the same thing twice

Ez a funkció az F# RFC FS-1076-ot implementálja.

Előzetes verzió: az egyéni kulcsszavak túlterhelése számítási kifejezésekben

A számítási kifejezések hatékony funkciókat jelentenek a kódtárak és a keretrendszerek szerzői számára. Lehetővé teszik, hogy jelentősen javítsa az összetevők kifejezőképességét azáltal, hogy lehetővé teszi, hogy jól ismert tagokat definiáljon, és DSL-t alakítson ki a tartomány számára, amelyben dolgozik.

Az F# 5 előzetes verzióval támogatja az egyéni műveletek túlterhelését a Számítási kifejezésekben. Lehetővé teszi a következő kód írását és használatát:

open System

type InputKind =
    | Text of placeholder:string option
    | Password of placeholder: string option

type InputOptions =
  { Label: string option
    Kind : InputKind
    Validators : (string -> bool) array }

type InputBuilder() =
    member t.Yield(_) =
      { Label = None
        Kind = Text None
        Validators = [||] }

    [<CustomOperation("text")>]
    member this.Text(io, ?placeholder) =
        { io with Kind = Text placeholder }

    [<CustomOperation("password")>]
    member this.Password(io, ?placeholder) =
        { io with Kind = Password placeholder }

    [<CustomOperation("label")>]
    member this.Label(io, label) =
        { io with Label = Some label }

    [<CustomOperation("with_validators")>]
    member this.Validators(io, [<ParamArray>] validators) =
        { io with Validators = validators }

let input = InputBuilder()

let name =
    input {
    label "Name"
    text
    with_validators
        (String.IsNullOrWhiteSpace >> not)
    }

let email =
    input {
    label "Email"
    text "Your email"
    with_validators
        (String.IsNullOrWhiteSpace >> not)
        (fun s -> s.Contains "@")
    }

let password =
    input {
    label "Password"
    password "Must contains at least 6 characters, one number and one uppercase"
    with_validators
        (String.exists Char.IsUpper)
        (String.exists Char.IsDigit)
        (fun s -> s.Length >= 6)
    }

A módosítás előtt megírhatja a InputBuilder típust, de a példában használt módon nem használhatja. Mivel a túlterhelések, az opcionális paraméterek és a típusok System.ParamArray engedélyezettek, minden úgy működik, ahogy elvárná.

Ez a funkció az F# RFC FS-1056-ot implementálja.