Share via


F# 5'teki yenilikler

F# 5, F# diline ve F# Etkileşimli'ye çeşitli geliştirmeler ekler. .NET 5 ile yayınlanır.

En son .NET SDK'sını .NET indirmeleri sayfasından indirebilirsiniz.

Kullanmaya başlayın

F# 5 tüm .NET Core dağıtımlarında ve Visual Studio araçlarında kullanılabilir. Daha fazla bilgi için bkz . F# kullanmaya başlama.

F# betiklerinde paket başvuruları

F# 5, söz dizimi içeren #r "nuget:..." F# betiklerinde paket başvuruları için destek sunar. Örneğin, aşağıdaki paket başvurularını göz önünde bulundurun:

#r "nuget: Newtonsoft.Json"

open Newtonsoft.Json

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

printfn $"{JsonConvert.SerializeObject o}"

Paketin adından sonra aşağıdaki gibi açık bir sürüm de sağlayabilirsiniz:

#r "nuget: Newtonsoft.Json,11.0.1"

Paket başvuruları, ML.NET gibi yerel bağımlılıklara sahip paketleri destekler.

Paket başvuruları, bağımlılara .dllbaşvurma hakkında özel gereksinimleri olan paketleri de destekler. Örneğin, kullanıcıların F# Interactive'de daha önce başvurulmadan önce FParsec.dll bağımlısına FParsecCS.dll el ile başvurulmasını sağlamak için kullanılan FParsec paketi. Bu artık gerekli değildir ve pakete aşağıdaki gibi başvurabilirsiniz:

#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"

Bu özellik F# Araçları RFC FST-1027'yi uygular. Paket başvuruları hakkında daha fazla bilgi için F# Etkileşimli öğreticisine bakın.

Dize ilişkilendirme

F# ilişkilendirmeli dizeler, C# veya JavaScript ilişkilendirmeli dizelerine oldukça benzerdir, bu nedenle bir dize değişmez değeri içinde "delikler" içinde kod yazmanıza olanak tanır. Basit bir örnek verelim:

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

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

Ancak F# ilişkilendirmeli dizeler, aynı işlev gibi, ilişkilendirilmiş bağlamın içindeki bir ifadenin sprintf belirli bir türe uygun olmasını zorunlu kılmak için yazılan ilişkilendirmelere de olanak tanır. Aynı biçim tanımlayıcılarını kullanır.

let name = "Phillip"
let age = 29

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

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

Önceki yazılan ilişkilendirme örneğinde, %s ilişkilendirmenin türünde stringolmasını gerektirirken %d , ilişkilendirmenin bir integerolması gerekir.

Ayrıca, herhangi bir rastgele F# ifadesi (veya ifadeler) ilişkilendirme bağlamının yanına yerleştirilebilir. Daha karmaşık bir ifade yazmak bile mümkündür, örneğin:

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]
}
"""

Bunu pratikte çok fazla yapmanızı önermesek de.

Bu özellik F# RFC FS-1001'i uygular.

Nameof desteği

F# 5, kullanıldığı simgeyi çözümleyen ve adını F# kaynağında üreten işlecini destekler nameof . Bu, günlüğe kaydetme gibi çeşitli senaryolarda kullanışlıdır ve günlük kaydınızı kaynak koddaki değişikliklere karşı korur.

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

Son satır bir özel durum oluşturur ve hata iletisinde "ay" gösterilir.

Neredeyse her F# yapısının adını alabilirsiniz:

module M =
    let f x = nameof x

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

Son üç ekleme işleçlerin çalışma şekline yapılan değişikliklerdir: genel tür parametreleri için formun nameof<'type-parameter> eklenmesi ve desen eşleştirme ifadesinde desen olarak kullanılabilmesi nameof .

Bir işlecin adını almak, kaynak dizesini verir. Derlenmiş forma ihtiyacınız varsa, işlecin derlenmiş adını kullanın:

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

Tür parametresinin adını almak için biraz farklı bir söz dizimi gerekir:

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

Bu, ve typedefof<'T> işleçlerine typeof<'T> benzer.

F# 5, ifadelerde match kullanılabilecek bir nameof desen için de destek ekler:

[<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

Yukarıdaki kod, eşleştirme ifadesindeki dize değişmez değeri yerine 'nameof' kullanır.

Bu özellik F# RFC FS-1003 uygular.

Açık tür bildirimleri

F# 5, açık tür bildirimleri için de destek ekler. Açık tür bildirimi, F# semantiğine uyması için bazı farklı söz dizimi ve biraz farklı davranışlar dışında C# dilinde statik sınıf açmaya benzer.

Açık tür bildirimleriyle, içindeki statik içeriği ortaya çıkarmak için herhangi bir tür kullanabilirsiniz open . Ayrıca, içeriklerini kullanıma açmak için F#tanımlı birleşimleri ve kayıtları da kullanabilirsiniz open . Örneğin, bir modülde tanımlanmış bir birleşime sahipseniz ve durumlarına erişmek istiyorsanız ancak modülün tamamını açmak istemiyorsanız bu yararlı olabilir.

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

C# dilinden farklı olarak, aynı ada sahip bir üyeyi kullanıma sunan iki türde olduğunuzda open type , son türdeki openüye diğer adı gölgeler. Bu, zaten var olan gölgelendirmenin etrafındaki F# semantiğiyle tutarlıdır.

Bu özellik F# RFC FS-1068'i uygular.

Yerleşik veri türleri için tutarlı dilimleme davranışı

F# 5'e kadar tutarlı olmaması için kullanılan yerleşik FSharp.Core veri türlerini (dizi, liste, dize, 2B dizi, 3B dizi, 4B dizi) dilimleme davranışı. Bazı uç durum davranışları özel durum oluştururken bazıları bunu yapmaz. F# 5'te, tüm yerleşik türler artık oluşturulması imkansız olan dilimler için boş dilimler döndürür:

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)]

Bu özellik F# RFC FS-1077'i uygular.

FSharp.Core'da 3B ve 4B diziler için sabit dizin dilimleri

F# 5, yerleşik 3B ve 4B dizi türlerinde sabit bir dizinle dilimleme desteği sunar.

Bunu göstermek için aşağıdaki 3B diziyi göz önünde bulundurun:

z = 0

x\y 0 1
0 0 1
1 2 3

z = 1

x\y 0 1
0 4 5
1 6 7

Dizideki dilimi [| 4; 5 |] ayıklamak isterseniz ne olur? Bu artık çok basit!

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

Bu özellik F# RFC FS-1077b uygular.

F# tırnak işaretleri geliştirmeleri

F# kod teklifleri artık tür kısıtlama bilgilerini koruyabilme özelliğine sahiptir. Aşağıdaki örneği inceleyin:

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

İşlev tarafından inline oluşturulan kısıtlama kod teklifinde tutulur. İşlevin negate alıntılanan formu artık değerlendirilebilir.

Bu özellik F# RFC FS-1071'i uygular.

Uygulamalı Hesaplama İfadeleri

Hesaplama ifadeleri (CE) günümüzde "bağlamsal hesaplamaları" veya daha işlevsel programlama dostu terminolojide monatik hesaplamaları modellemek için kullanılır.

F# 5, farklı bir hesaplama modeli sunan uygulamalı CE'leri tanıtır. Uygulamalı CE'ler, her hesaplamanın bağımsız olması ve sonuçlarının sonunda birikmesi koşuluyla daha verimli hesaplamalar sağlar. Hesaplamalar birbirinden bağımsız olduğunda, bunlar da önemsiz olarak paralelleştirilebilir ve CE yazarlarının daha verimli kitaplıklar yazmasına olanak tanır. Ancak bu avantaj bir kısıtlamaya neden olur: Daha önce hesaplanan değerlere bağlı hesaplamalara izin verilmez.

Aşağıdaki örnekte, türü için temel bir uygulama CE'sı gösterilmektedir Result .

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

Bugün kitaplıklarında CE'leri kullanıma sunan bir kitaplık yazarıysanız, dikkat etmeniz gereken bazı ek noktalar vardır.

Bu özellik F# RFC FS-1063'i uygular.

Arabirimler farklı genel örneklemelerde uygulanabilir

Artık farklı genel örneklemelerde aynı arabirimi uygulayabilirsiniz:

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"

Bu özellik F# RFC FS-1031'i uygular.

Varsayılan arabirim üyesi tüketimi

F# 5, varsayılan uygulamalarla arabirimleri kullanmanıza olanak tanır.

C# dilinde tanımlanan bir arabirimi şöyle düşünün:

using System;

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

Bunu F# dilinde arabirim uygulama standart yollarından herhangi biri aracılığıyla kullanabilirsiniz:

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

Bu, kullanıcıların varsayılan bir uygulamayı kullanabilmesini beklediklerinde C# kodundan ve modern C# dilinde yazılmış .NET bileşenlerinden güvenle yararlanmanızı sağlar.

Bu özellik F# RFC FS-1074'i uygular.

Boş değer türleriyle basitleştirilmiş birlikte çalışma

Null atanabilir (değer) türleri (geçmiş olarak Null Atanabilir Türler olarak adlandırılır) uzun zamandır F# tarafından destekleniyordu, ancak her değer geçirmek istediğinizde veya Nullable<SomeType> sarmalayıcı oluşturmanız Nullable gerektiğinden, bunlarla etkileşim kurmak geleneksel olarak biraz zor olmuştur. Şimdi, hedef tür eşleşiyorsa derleyici örtük olarak bir Nullable<ThatValueType> değer türüne dönüştürür. Aşağıdaki kod artık mümkündür:

#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")))

Bu özellik F# RFC FS-1075'i uygular.

Önizleme: ters dizinler

F# 5 ayrıca ters dizinlere izin vermek için bir önizleme sunar. Söz dizimi ^idx şeklindedir. Bir öğenin 1 değerini listenin sonundan şu şekilde yapabilirsiniz:

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

Kendi türleriniz için ters dizinler de tanımlayabilirsiniz. Bunu yapmak için aşağıdaki yöntemi uygulamanız gerekir:

GetReverseIndex: dimension: int -> offset: int

Aşağıda türü için bir örnek verilmişti Span<'T> :

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

Bu özellik F# RFC FS-1076 uygular.

Önizleme: hesaplama ifadelerinde özel anahtar sözcüklerin aşırı yüklemeleri

Hesaplama ifadeleri, kitaplık ve çerçeve yazarları için güçlü bir özelliktir. Bunlar, iyi bilinen üyeleri tanımlamanıza ve çalıştığınız etki alanı için BIR DSL oluşturmanıza olanak tanıyarak bileşenlerinizin ifade düzeyini büyük ölçüde geliştirmenize olanak sağlar.

F# 5, Hesaplama İfadeleri'nde özel işlemleri aşırı yüklemeye yönelik önizleme desteği ekler. Aşağıdaki kodun yazılmasına ve tüketilmesine izin verir:

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

Bu değişiklik öncesinde, türü olduğu gibi yazabilirsiniz InputBuilder , ancak örnekte kullanıldığı gibi kullanamazsınız. Aşırı yüklemeler, isteğe bağlı parametreler ve şimdi System.ParamArray türlere izin verildiğinden, her şey beklediğiniz gibi çalışır.

Bu özellik F# RFC FS-1056 uygular.