F# 5'te yapılanlar
F# 5, F# diline ve diline çeşitli geliştirmeler F# Etkileşimli. .NET 5 ile yayınlandı.
.NET indirmeleri sayfasından en son .NET SDK'larını indirebilirsiniz.
başlarken
F# 5 tüm .NET Core dağıtımlarında ve Visual Studio kullanılabilir. Daha fazla bilgi için bkz. F# ile Kullanmaya başlayın bilgi için bkz.
F# betikleri içinde paket başvuruları
F# 5, söz dizimi içeren F# betikleri içinde paket başvuruları için destek #r "nuget:..." getirir. Örneğin, aşağıdaki paket başvurularını dikkate alalım:
#r "nuget: Newtonsoft.Json"
open Newtonsoft.Json
let o = {| X = 2; Y = "Hello" |}
printfn $"{JsonConvert.SerializeObject o}"
Ayrıca, paketin adının ardından aşağıdaki gibi açık bir sürüm de sabilirsiniz:
#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ı kaynaklara başvuruyla ilgili özel gereksinimleri olan paketleri .dll de destekler. Örneğin, kullanıcıların bağımlı olduğundan emin olmasını sağlamak için kullanılan FParsec paketine ilk olarak bu pakette FParsecCS.dll başvurulmadan önce FParsec.dll F# Etkileşimli. 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# Tooling RFC FST-1027 'yi uygulamaya almaktadır. Paket başvuruları hakkında daha fazla bilgi için bkz. F# Etkileşimli.
Dize ilişkilendirme
F# irdelenmiş dizeleri, C# veya JavaScript irdelenmiş dizelere oldukça benzer ve bu dizeler, bir dize değişmez değeri içindeki "delikler" içinde kod yazmana izin verir. 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# irdelenmiş dizeleri, aynı işlevi gibi, irdelenmiş bağlam içindeki bir ifadenin belirli bir türe uygun olduğunu zorunlu kıldıklarına da sprintf olanak sağlar. Aynı biçim belirleyicilerini 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}"
Yukarıdaki türe sahip ilişkilendirme örneğinde, ilişkilendirmenin türünde olması gerekirken , ilişkilendirmenin %s string bir olması %d integer gerekir.
Buna ek olarak, herhangi bir rastgele F# ifadesi (veya ifadeleri) ilişkilendirme bağlamının bir tarafına yer olabilir. Şu şekilde daha karmaşık bir ifade yazmak bile mümkündür:
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]
}
"""
Ancak uygulamada bunu çok fazla yapmak önerilmez.
Bu özellik F# RFC FS-1001'i uygulamaya almaktadır.
nameof desteği
F# nameof 5, için kullanılan sembolü çözümleyip adını F# kaynağında üreten işleci destekler. Bu, günlüğe kaydetme gibi çeşitli senaryolarda yararlıdır ve günlük kaydınızı kaynak kodundaki 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 "month" gösterilir.
Neredeyse her F# yapısı için bir ad ve sonra da şu adlardan bir ad vesersiniz:
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 ve desen eşleşme ifadesinde desen nameof<'type-parameter> nameof olarak kullanma özelliği.
Bir işlecinin adını almak, kaynak dizesini verir. Derlenmiş forma ihtiyacınız varsa, bir işlecinin 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 typeof<'T> typedefof<'T> işleçleri ile benzerdir.
F# 5 ayrıca ifadelerde nameof kullanılmaktadır bir desen için match 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şme ifadesinde dize değişmez değeri yerine 'nameof' kullanır.
Bu özellik F# RFC FS-1003'ü uygulamaya almaktadır.
Tür bildirimlerini açma
F# 5 ayrıca açık tür bildirimleri için destek ekler. Açık tür bildirimi, F# semantiğine sığacak farklı söz dizimi ve biraz farklı davranışlar dışında C# içinde statik bir sınıf açmaya benzer.
Açık tür bildirimleriyle, içindeki statik open içerikleri ortaya çıkarmak için herhangi bir türü kullanabilirsiniz. Ayrıca, içeriklerini open ortaya çıkarmak için F# tanımlı union'ları ve kayıtları da sabilirsiniz. Örneğin, bir modülde tanımlanmış bir birliktelik varsa ve örneklerine erişmek istiyor 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# türünden farklı olarak, aynı adla bir üyeyi ortaya çıkaran iki türe sahipseniz, son tür olan üye diğer adı open type open gölgeler. Bu, zaten mevcut olan gölgelamanın F# semantiğiyle tutarlıdır.
Bu özellik F# RFC FS-1068'i uygulamaya almaktadır.
Yerleşik veri türleri için tutarlılicing davranışı
F# 5'den önce tutarlı olmak için kullanılan yerleşik veri türlerini FSharp.Core (dizi, liste, dize, 2D dizi, 3D dizi, 4D dizi)licing davranışı. Bazı uç durum davranışları bir özel durum oluşturdu ve bazıları bunu geri alamdı. F# 5'te, tüm yerleşik türler artık oluşturulacak mümkün olmayan dilimler için boş dilimler üretir:
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'yi uygulamaya almaktadır.
FSharp.Core'da 3D ve 4D diziler için sabit dizin dilimleri
F# 5, yerleşik 3D ve 4D dizi türlerinde sabit bir dizinlelicing desteği getirir.
Bunu göstermek için aşağıdaki 3D dizisini göz önünde bulundurarak:
z = 0
| x\y | 0 | 1 |
|---|---|---|
| 0 | 0 | 1 |
| 1 | 2 | 3 |
z = 1
| x\y | 0 | 1 |
|---|---|---|
| 0 | 4 | 5 |
| 1 | 6 | 7 |
Diziden dilimi ayıklamak için [| 4; 5 |] 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 'yi uygulamaya almaktadır.
F# tırnak geliştirmeleri
F# kod tırnakları artık tür kısıtlama bilgilerini koruyabilme özelliğine sahip. 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 oluşturulan inline kısıtlama kod tırnak içinde korunur. İşlevin negate tırnak içine alan formu artık değerlendirebilirsiniz.
Bu özellik F# RFC FS-1071'i uygulamaya almaktadır.
Uygulamalı Hesaplama İfadeleri
Hesaplama ifadeleri (CDE) günümüzde "bağlamsal hesaplamaları" modellemek veya daha işlevsel programlama kullanımı kolay terminolojide monadik hesaplamalar için kullanılır.
F# 5, farklı bir hesaplama modeli sunan uygulamalı CE'leri tanıtıyor. Uygulamalı CE'ler, her hesaplamanın bağımsız olduğu ve sonuçların sonunda toplanıyor olduğu şartıyla daha verimli hesaplamalar sağlar. Hesaplamalar birbirinden bağımsız olduğunda, bunlar da önemsiz bir şekilde paralelleştirilebilir ve CE yazarlarının daha verimli kitaplıklar yazmasına olanak sağlar. Ancak bu avantajın bir kısıtlaması vardır: önceden hesaplanan değerlere bağlı hesaplamalara izin verilmez.
Aşağıdaki örnekte, tür için temel bir uygulamalı CE Result gösterir.
// 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 ortaya çıkaran bir kitaplık yazarıysanız dikkat edilmesi gereken bazı ek noktalar vardır.
Bu özellik F# RFC FS-1063'ü uygulamaya almaktadır.
Arabirimler farklı genel örneklerde uygulanıyor
Artık aynı arabirimi farklı genel örneklerde de gerçekleştirebilirsiniz:
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 uygulamaya almaktadır.
Varsayılan arabirim üyesi tüketimi
F# 5, varsayılan uygulamalarıyla arabirimleri tüketmenizi sağlar.
C# ile tanımlanan bir arabirimi şu şekilde düşünün:
using System;
namespace CSharp
{
public interface MyDim
{
public int Z => 0;
}
}
Bir arabirim uygulamanın standart yollarının herhangi biri aracılığıyla F# içinde bunu tüketebilirsiniz:
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 uygulamanın kullanılabilir olmasını beklenilen modern C# ile yazılmış C# kodu ve .NET bileşenlerinden güvenli bir şekilde yararlanmaya olanak sağlar.
Bu özellik F# RFC FS-1074'ü uygulamaya almaktadır.
Boş değer değer türleriyle basitleştirilmiş birlikte çalışma
Null değer (değer) türleri (null değere değiştirilebilir Türler olarak da adlandırılan) uzun süredir F# tarafından desteklense de, bir değeri her geçmek için bir veya sarmalayıcı oluşturmak zorunda olacağınız için bu türlerle etkileşim kurmak geleneksel olarak biraz zor Nullable Nullable<SomeType> oldu. Artık derleyici, hedef tür eş olursa bir değer türünü örtülü Nullable<ThatValueType> olarak bir 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 uygulamaya almaktadır.
Önizleme: ters dizinler
F# 5 ayrıca ters dizinlere izin veren bir önizleme de sağlar. Söz dizimi ^idx şeklindedir. Listenin sonundan 1. öğe değerini şu şekilde ekleyebilirsiniz:
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 uygulamalısiniz:
GetReverseIndex: dimension: int -> offset: int
Türü için bir örnek: 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 'yi uygulamaya almaktadır.
Ö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 üyeler tanımlamanıza ve üzerinde çalışmakta olduğunuz etki alanı için DSL oluşturmanıza olanak tanıarak bileşenlerinizin ifadesini büyük ölçüde geliştirmenize olanak sağlar.
F# 5, Hesaplama İfadeleri'ne özel işlemlerin aşırı yüklenmesi için önizleme desteği ekler. Aşağıdaki kodun yazilmesine ve tüketilmesine olanak sağlar:
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 yazabilir, ancak örnekte olduğu gibi InputBuilder kullanamz. Aşırı yüklemelere, isteğe bağlı parametrelere ve artık türlere izin System.ParamArray verilmiyor, her şey beklediğiniz gibi çalışır.
Bu özellik F# RFC FS-1056 'yi uygulamaya almaktadır.