Not
Bu sayfaya erişim yetkilendirme gerektiriyor. Oturum açmayı veya dizinleri değiştirmeyi deneyebilirsiniz.
Bu sayfaya erişim yetkilendirme gerektiriyor. Dizinleri değiştirmeyi deneyebilirsiniz.
Bu belge, F# Bileşen Tasarım Yönergeleri, v14, Microsoft Research ve başlangıçta F# Software Foundation tarafından seçilmiş ve bakımı yapılan bir sürüme dayalı olarak F# programlama için bir bileşen tasarım yönergeleri kümesidir.
Bu belgede F# programlama hakkında bilgi sahibi olduğunuz varsayılır. Bu kılavuzun çeşitli sürümleriyle ilgili katkıları ve yararlı geri bildirimleri için F# topluluğuna çok teşekkür ederiz.
Genel bakış
Bu belge, F# bileşeni tasarımı ve kodlaması ile ilgili bazı sorunları ele alır. Bir bileşen aşağıdakilerden herhangi biri anlamına gelebilir:
- F# projenizde, o projede dış tüketicileri bulunan bir katman.
- Derleme sınırları boyunca F# koduyla kullanıma yönelik bir kitaplık.
- Derleme sınırları boyunca herhangi bir .NET dili tarafından kullanılmak üzere tasarlanmış bir kitaplık.
- NuGetgibi bir paket deposu aracılığıyla dağıtıma yönelik bir kitaplık.
Bu makalede açıklanan teknikler, beş iyi F# kodu ilkesiizler ve böylece hem işlevsel hem de nesne programlamayı uygun şekilde kullanır.
Metodolojiden bağımsız olarak, bileşen ve kitaplık tasarımcısı geliştiriciler tarafından en kolay kullanılabilen bir API oluşturmaya çalışırken çeşitli pratik ve prosaic sorunlarla karşı karşıyadır. .NET Kitaplığı Tasarım Yönergeleri'nin vicdani uygulaması, sizi kullanıma uygun tutarlı bir API kümesi oluşturmaya yönlendirir.
Genel yönergeler
Kitaplığın hedef kitlesine bakılmaksızın F# kitaplıkları için geçerli olan birkaç evrensel yönerge vardır.
.NET Kitaplığı Tasarım Yönergelerini öğrenin
Yaptığınız F# kodlama türü ne olursa olsun, .NET Kitaplığı Tasarım Yönergelerihakkında çalışan bir bilgiye sahip olmak değerlidir. Diğer F# ve .NET programcılarının çoğu bu yönergeleri bilir ve .NET kodunun bunlara uymasını bekler.
.NET Kitaplığı Tasarım Yönergeleri adlandırma, sınıfları ve arabirimleri tasarlama, üye tasarımı (özellikler, yöntemler, olaylar vb.) ve daha fazlası hakkında genel yönergeler sağlar ve çeşitli tasarım yönergeleri için yararlı bir ilk başvuru noktasıdır.
Kodunuza XML belgeleri açıklamaları ekleme
Genel API'lerle ilgili XML belgeleri, kullanıcıların bu türleri ve üyeleri kullanırken harika IntelliSense ve Quickinfo elde edebilmelerini sağlar ve kitaplık için belge dosyaları oluşturmayı etkinleştirir. xmldoc açıklamalarında ek işaretleme için kullanılabilecek çeşitli xml etiketleri hakkında XML Belgeleri bakın.
/// A class for representing (x,y) coordinates
type Point =
/// Computes the distance between this point and another
member DistanceTo: otherPoint:Point -> float
Kısa form XML açıklamalarını (/// comment) veya standart XML açıklamalarını (///<summary>comment</summary>) kullanabilirsiniz.
Kararlı kitaplık ve bileşen API'leri için açık imza dosyalarını (.fsi) kullanmayı göz önünde bulundurun
F# kitaplığında açık imza dosyalarının kullanılması, kitaplığınızın tam genel yüzeyini bildiğinizden emin olmanıza yardımcı olan ve genel belgelerle iç uygulama ayrıntıları arasında temiz bir ayrım sağlayan genel API'nin kısa bir özetini sağlar. İmza dosyaları, hem uygulama hem de imza dosyalarında değişiklik yapılmasını gerektirerek genel API'nin değiştirilmesine sürtüşmeler ekler. Sonuç olarak, imza dosyaları genellikle yalnızca bir API yerleşik hale geldiğinde ve artık önemli ölçüde değişmesi beklenmediğinde devreye alınmalıdır.
.NET'te dizeleri kullanmak için en iyi yöntemleri izleyin
Proje kapsamı bunu gerektirdiğinde .NET'te Dizeleri Kullanmak için En İyi Uygulamalar yönergeleri izleyin. Özellikle, dizelerin dönüştürülmesi ve karşılaştırılması sırasında (uygun olduğunda) kültürel amacı açıkça belirtmek.
F# için tasarlanmış kitaplıklar için yönergeler
Bu bölümde, F# geliştiricilerine yönelik genel API'leri açığa çıkaran kitaplıklar geliştirmeye yönelik öneriler sunulmaktadır; diğer bir ifadeyle, F# geliştiricileri tarafından kullanılması amaçlanan genel API'leri açığa çıkaran kitaplıklar. F# için özel olarak geçerli olan çeşitli kitaplık tasarımı önerileri vardır. Aşağıdaki belirli öneriler yoksa başvuru kılavuzu .NET Kitaplığı Tasarım Yönergeleri'dir.
Adlandırma kuralları
.NET adlandırma ve büyük harfe çevirme kurallarını kullanma
Aşağıdaki tabloda .NET adlandırma ve büyük harf kullanımı kuralları gösterilmektedir. F# yapılarını da içerecek küçük eklemeler vardır. Bu öneriler özellikle F#-F# sınırlarını aşan, .NET BCL'den gelen deyimlere ve kitaplıkların çoğuna uyan API'lere yöneliktir.
| İnşa etmek | Durum | Kısım | Örnekler | Notlar |
|---|---|---|---|---|
| Beton tipler | PascalCase | İsim/ sıfat | Liste, Çift, Karmaşık | Somut türler yapılar, sınıflar, numaralandırmalar, temsilciler, kayıtlar ve birleşimlerdir. OCaml'de tür adları geleneksel olarak küçük harf olsa da, F# türler için .NET adlandırma düzenini benimsemiştir. |
| DLL'ler | PascalCase | Fabrikam.Core.dll | ||
| Birleşim etiketleri | PascalCase | İsim | Bazıları, Ekle, Başarılı | Genel API'lerde ön ek kullanmayın. İsteğe bağlı olarak dahili bir ön ek kullanın; örneğin, "type Teams = TAlpha | TBeta | TDelta". |
| Etkinlik | PascalCase | Fiil | DeğerDeğişti / DeğerDeğişiyor | |
| Özel durum | PascalCase | WebException | Ad "Exception" ile bitmelidir. | |
| Alan | PascalCase | İsim | Mevcut İsim | |
| Arabirim türleri | PascalCase | İsim/ sıfat | IDisposable (atılabilir arayüz) | Ad "I" ile başlamalıdır. |
| Yöntem | PascalCase | Fiil | ToString | |
| Namespace | PascalCase | Microsoft.FSharp.Core | Genel olarak <Organization>.<Technology>[.<Subnamespace>]kullanın, ancak teknoloji kuruluşdan bağımsızsa kuruluşu bırakın. |
|
| Parametreler | camelCase | İsim | türAdı, dönüştür, aralık | |
| let değerler (dahili) | camelCase veya PascalCase | İsim/ fiil | getValue, myTable | |
| değerleri belirle (dış) | camelCase veya PascalCase | İsim/fiil | Liste.haritala, Tarihler.Bugün | Genellikle, let-bound değerleri geleneksel işlevsel tasarım desenlerini takip ederken herkese açıktır. Ancak, tanımlayıcı diğer .NET dillerinden kullanılabildiğinde genellikle PascalCase kullanın. |
| Mülk | PascalCase | İsim/ sıfat | IsEndOfFile, ArkaPlan Rengi | Boolean özellikleri genellikle Is ve Can kullanır ve IsEndOfFile'da olduğu gibi olumlu olmalıdır, IsNotEndOfFile değil. |
Kısaltmalardan kaçının
.NET yönergeleri kısaltma kullanımını önerilmez (örneğin, "OnButtonClickyerine OnBtnClick kullanın"). "Asenkron" için Async gibi sık kullanılan kısaltmalar kabul görür. Bu kılavuz, işlevsel programlama için bazen göz ardı edilir; örneğin, List.iter "yinele" için bir kısaltma kullanır. Bu nedenle, kısaltmaların kullanılması F#-F# programlamada daha fazla tolere edilme eğilimindedir, ancak genel bileşen tasarımında genellikle kaçınılmalıdır.
Ad çakışmalarını önlemek için büyük/küçük harf kullanımından kaçının
.NET yönergeleri, ad çakışmalarını çözmek için sadece büyük/küçük harf kullanımının yeterli olmadığını belirtir, çünkü bazı istemci dilleri (örneğin, Visual Basic) büyük/küçük harf duyarlı değildir.
Uygun durumlarda kısaltmaları kullanma
XML gibi kısaltmalar kısaltma değildir ve .NET kitaplıklarında kapsüllenmemiş biçimde (Xml) yaygın olarak kullanılır. Yalnızca iyi bilinen, yaygın olarak tanınan kısaltmalar kullanılmalıdır.
Genel parametre adları için PascalCase kullanma
PascalCase'i F#'ye yönelik kitaplıklar dahil olmak üzere genel API'lerdeki genel parametre adları için kullanın. Rastgele genel parametreler için özellikle T, U, T1, T2 gibi adları kullanın ve belli adların uygun olduğu durumlarda, F# ile ilişkili kütüphaneler için Key, Value, Arg gibi adları tercih edin (ancak örnek olarak, TKeykullanmayın).
F# modüllerinde genel işlevler ve değerler için PascalCase veya camelCase kullanın
camelCase, doğrudan kullanılmak üzere tasarlanmış açık işlevler (örneğin, invalidArg) ve "standart koleksiyon işlevleri" (örneğin, List.map) için kullanılır. Her iki durumda da işlev adları dildeki anahtar sözcükler gibi davranır.
Nesne, Tür ve Modül tasarımı
Türlerinizi ve modüllerinizi içermek için ad alanlarını veya modülleri kullanma
Bir bileşendeki her F# dosyası bir ad alanı bildirimi veya modül bildirimi ile başlamalıdır.
namespace Fabrikam.BasicOperationsAndTypes
type ObjectType1() =
...
type ObjectType2() =
...
module CommonOperations =
...
veya
module Fabrikam.BasicOperationsAndTypes
type ObjectType1() =
...
type ObjectType2() =
...
module CommonOperations =
...
Kodu en üst düzeyde düzenlemek için modülleri ve ad alanlarını kullanma arasındaki farklar şunlardır:
- Ad alanları birden çok dosyaya yayılabilir
- İç modülde yer almadıkları sürece ad alanları F# işlevleri içeremez
- Belirli bir modülün kodu tek bir dosya içinde bulunmalıdır
- Üst düzey modüller, iç modüle gerek kalmadan F# işlevleri içerebilir
Üst düzey ad alanı veya modül arasındaki seçim kodun derlenmiş biçimini etkiler ve bu nedenle API'nizin sonunda F# kodunun dışında tüketilmesi durumunda diğer .NET dillerinden görünümü etkiler.
Nesne türlerine yönelik işlemler için yöntemleri ve özellikleri kullanma
Nesnelerle çalışırken, tüketilebilir işlevselliğin bu türdeki yöntemler ve özellikler olarak uygulandığını güvence altına almak en iyisidir.
type HardwareDevice() =
member this.ID = ...
member this.SupportedProtocols = ...
type HashTable<'Key,'Value>(comparer: IEqualityComparer<'Key>) =
member this.Add(key, value) = ...
member this.ContainsKey(key) = ...
member this.ContainsValue(value) = ...
Belirli bir üyenin işlevselliğinin büyük bir kısmının bu üyeye uygulanması şart değildir, ancak bu işlevselliğin tüketilebilir parçası olmalıdır.
Değişken durumu kapsüllemek için sınıfları kullanın
F# dilinde, bu yalnızca durumun zaten bir kaplama, sıra ifadesi veya asenkron hesaplama gibi başka bir dil yapısı tarafından kapsüllenmediği durumlarda yapılması gerekir.
type Counter() =
// let-bound values are private in classes.
let mutable count = 0
member this.Next() =
count <- count + 1
count
İlgili işlemleri gruplandırmak için arabirimleri kullanma
Bir işlem kümesini temsil etmek için arabirim türlerini kullanın. Bu, işlev tanımlama demetleri veya işlev kayıtları gibi diğer seçenekler için tercih edilir.
type Serializer =
abstract Serialize<'T> : preserveRefEq: bool -> value: 'T -> string
abstract Deserialize<'T> : preserveRefEq: bool -> pickle: string -> 'T
Tercihen:
type Serializer<'T> = {
Serialize: bool -> 'T -> string
Deserialize: bool -> string -> 'T
}
Arabirimler, Functor'ların normalde size vereceklerini elde etmek için kullanabileceğiniz .NET'teki birinci sınıf kavramlardır. Ayrıca, varoluşsal türleri programınıza kodlamak için kullanılabilir ve işlev kayıtları bunu yapamaz.
Koleksiyonlar üzerinde işlem gösteren işlevleri gruplandırmak için modül kullanma
Bir koleksiyon türü tanımlarken, yeni koleksiyon türleri için CollectionType.map ve CollectionType.iter) gibi standart bir işlem kümesi sağlamayı göz önünde bulundurun.
module CollectionType =
let map f c =
...
let iter f c =
...
Böyle bir modül eklerseniz FSharp.Core'da bulunan işlevler için standart adlandırma kurallarını izleyin.
Özellikle matematik ve DSL kitaplıklarında yaygın, kurallı işlevlerin işlevlerini gruplandırmak için modül kullanma
Örneğin Microsoft.FSharp.Core.Operators, FSharp.Core.dlltarafından sağlanan en üst düzey işlevlerin (abs ve singibi) otomatik olarak açılan bir koleksiyonudur.
Benzer şekilde, istatistik kitaplığı erf ve erfcişlevlerine sahip bir modül içerebilir ve burada bu modül açıkça veya otomatik olarak açılacak şekilde tasarlanmıştır.
RequireQualifiedAccess kullanmayı ve AutoOpen özniteliklerini dikkatle uygulamayı göz önünde bulundurun
[<RequireQualifiedAccess>] özniteliğini bir modüle eklemek modülün açılmayabileceğini ve modülün öğelerine yapılan başvuruların açık nitelikli erişim gerektirdiğini gösterir. Örneğin, Microsoft.FSharp.Collections.List modülünde bu öznitelik bulunur.
Bu, modüldeki işlevlerin ve değerlerin diğer modüllerdeki adlarla çakışma olasılığı olan adlara sahip olması durumunda kullanışlıdır. Nitelikli erişim gerektirmek, bir kitaplığın uzun vadeli sürdürülebilirliğini ve geliştirilebilirliğini büyük ölçüde artırabilir.
[<RequireQualifiedAccess>] tarafından sağlananları (FSharp.Core, Seq, Listgibi) genişleten özel modüller için Array özniteliğinin olması kesinlikle önerilir; bu modüller F# kodunda yaygın olarak kullanılır ve bunlar üzerinde [<RequireQualifiedAccess>] tanımlanmıştır; daha genel olarak, bu tür modül gölgeler veya özniteliği olan diğer modülleri genişletmek için özniteliği olmayan özel modülleri tanımlamak önerilmez.
[<AutoOpen>] özniteliğini modüle eklemek, modülün içeren ad alanı açıldığında açılacağı anlamına gelir.
[<AutoOpen>] özniteliği, derlemeye başvurulduğunda otomatik olarak açılan bir modülü belirtmek için bir derlemeye de uygulanabilir.
Örneğin, MathsHeaven.Statistics istatistik kitaplığı, module MathsHeaven.Statistics.Operatorserf ve erfcişlevlerini içerebilir. Bu modülü [<AutoOpen>]olarak işaretlemek mantıklıdır. Bu, open MathsHeaven.Statistics bu modülü de açacağı ve erf ve erfc adlarını kapsama getireceği anlamına gelir.
[<AutoOpen>] başka bir iyi kullanımı, uzantı yöntemleri içeren modüller içindir.
[<AutoOpen>] aşırı kullanılması kirli ad alanlarına yol açar ve özniteliği dikkatli kullanılmalıdır. Belirli alanlardaki belirli kütüphaneler için [<AutoOpen>]'ın dikkatli kullanımı daha iyi kullanılabilirliğe yol açabilir.
İyi bilinen işleçlerin uygun olduğu sınıflarda işleç üyeleri tanımlamayı göz önünde bulundurun
Bazen sınıflar Vectors gibi matematiksel yapıları modellemek için kullanılır. Modellenen etki alanının iyi bilinen işleçleri olduğunda, bunları sınıfa özgü üye olarak tanımlamak yararlı olur.
type Vector(x: float) =
member v.X = x
static member (*) (vector: Vector, scalar: float) = Vector(vector.X * scalar)
static member (+) (vector1: Vector, vector2: Vector) = Vector(vector1.X + vector2.X)
let v = Vector(5.0)
let u = v * 10.0
Bu kılavuz, bu türler için genel .NET kılavuzuna karşılık gelir. Bununla birlikte, bu türlerin List.sumBy gibi üye kısıtlamaları olan F# işlevleri ve yöntemleriyle birlikte kullanılmasına olanak sağladığından, F# kodlamasında da önemli olabilir.
Diğer .NET dil tüketicileri için .NET dostu bir ad sağlamak amacıyla CompiledName kullanmayı göz önünde bulundurun.
Bazen F# tüketicileri için tek bir stilde bir şeyi adlandırmak isteyebilirsiniz (örneğin, modüle bağlı bir işlevmiş gibi görünmesi için küçük harfle statik bir üye gibi), ancak bir derlemede derlendiğinde ad için farklı bir stile sahip olabilirsiniz.
[<CompiledName>] özniteliğini kullanarak, derlemeyi kullanan F# olmayan kod için farklı bir stil sağlayabilirsiniz.
type Vector(x:float, y:float) =
member v.X = x
member v.Y = y
[<CompiledName("Create")>]
static member create x y = Vector (x, y)
let v = Vector.create 5.0 3.0
[<CompiledName>]kullanarak, derlemenin F# olmayan tüketicileri için .NET adlandırma kurallarını kullanabilirsiniz.
Üye işlevleri için aşırı yükleme yöntemini kullanın, bunu yapmak daha basit bir API sağlar
Yöntem aşırı yüklemesi, benzer işlevleri gerçekleştirmesi gerekebilecek ancak farklı seçenekler veya bağımsız değişkenler içeren bir API'yi basitleştirmeye yönelik güçlü bir araçtır.
type Logger() =
member this.Log(message) =
...
member this.Log(message, retryPolicy) =
...
F# dilinde, bağımsız değişken türleri yerine bağımsız değişken sayısında aşırı yükleme yapmak daha yaygındır.
Bu türlerin tasarım değişikliğinden geçmesi muhtemelse, kayıt ve birleşim türlerinin gösterimlerini gizleyin.
Nesnelerin somut temsillerini ortaya çıkarmaktan kaçının. Örneğin, DateTime değerlerinin somut gösterimi.NET kitaplık tasarımının dış, genel API'si tarafından gösterilmez. Çalışma zamanında, Ortak Dil Çalışma Zamanı tüm yürütme süresi boyunca kullanılacak taahhüt edilen uygulamayı bilir. Ancak derlenmiş kod, somut temsil konusunda bağımlılıkları kendisinde oluşturmaz.
Genişletilebilirlik için uygulama devralma kullanımından kaçının
F# dilinde uygulama devralma nadiren kullanılır. Ayrıca, devralma hiyerarşileri genellikle karmaşıktır ve yeni gereksinimler geldiğinde değiştirilmesi zordur. Devralma uygulaması, uyumluluk ve sorunun en iyi çözümü olduğu nadir durumlarda F# dilinde hala mevcuttur, ancak arabirim uygulaması gibi çok biçimlilik için tasarım yaparken F# programlarınızda alternatif teknikler aranmalıdır.
İşlev ve üye imzaları
Az sayıda ilişkisiz değeri döndürürken dönüş değerleri için demetleri kullanın
İşte, dönüş türünde bir tupl kullanmanın iyi bir örneği:
val divrem: BigInteger -> BigInteger -> BigInteger * BigInteger
Çok sayıda bileşen içeren veya bileşenlerin tek bir tanımlanabilir varlıkla ilişkili olduğu dönüş türleri için demet yerine adlandırılmış tür kullanmayı düşünün.
F# API sınırlarında zaman uyumsuz programlama için Async<T> kullanma
Eğer Operationdöndüren T adlı bir eşzamanlı işlem varsa, AsyncOperation döndürürse zaman uyumsuz işlem Async<T> olarak veya OperationAsyncdöndürürse Task<T> olarak adlandırılmalıdır. Yaygın olarak kullanılan ve Begin/End yöntemlerini sunan .NET türleri için, bu .NET API'lerine F# eşzamanlı olmayan programlama modelini sağlamak amacıyla uzantı yöntemlerini bir arayüz olarak yazmak için Async.FromBeginEnd kullanmayı değerlendirin.
type SomeType =
member this.Compute(x:int): int =
...
member this.AsyncCompute(x:int): Async<int> =
...
type System.ServiceModel.Channels.IInputChannel with
member this.AsyncReceive() =
...
Özel durum
Özel durumların, sonuçların ve seçeneklerin uygun kullanımı hakkında bilgi edinmek için bkz. hata yönetimi .
Uzantı Üyeleri
F#-F# bileşenlerine F# uzantısı üyelerini dikkatle uygulama
F# uzantısı üyeleri genellikle yalnızca kullanım modlarının çoğunda bir türle ilişkili iç işlemlerin kapatılmasında olan işlemler için kullanılmalıdır. Yaygın kullanımlardan biri, çeşitli .NET türleri için F# ile daha doğal API'ler sağlamaktır.
type System.ServiceModel.Channels.IInputChannel with
member this.AsyncReceive() =
Async.FromBeginEnd(this.BeginReceive, this.EndReceive)
type System.Collections.Generic.IDictionary<'Key,'Value> with
member this.TryGet key =
let ok, v = this.TryGetValue key
if ok then Some v else None
Birleşim Türleri
Ağaç yapısındaki veriler için sınıf hiyerarşileri yerine ayrımlı birleştirmeler kullanma
Ağaç benzeri yapılar özyinelemeli olarak tanımlanır. Bu, devralma ile tuhaf, ancak Ayrık Birleşimler ile daha zarif.
type BST<'T> =
| Empty
| Node of 'T * BST<'T> * BST<'T>
Ayrımcı Birleşimler ile ağaç benzeri verileri temsil etmek, desen eşleştirmede kapsamlılıktan yararlanmanızı da sağlar.
Birleşim türlerinde durum adları yeterince benzersiz olmayanlar için [<RequireQualifiedAccess>] kullanın.
Kendinizi, Ayrımcı Birlik durumları gibi farklı şeyler için en iyi ismin aynı isim olduğu bir etki alanında bulabilirsiniz.
[<RequireQualifiedAccess>] deyimlerinin sıralamasına bağlı gölgeleme nedeniyle kafa karıştırıcı hataları tetiklememek için durum adlarını ayırt etmek amacıyla open kullanabilirsiniz.
Bu türlerin tasarımının gelişmesi muhtemelse, ikili uyumlu API'ler için ayırt edici birliklerin gösterimlerini gizleyin.
Birleşim türleri, özlü bir programlama modeli sağlamak için F# desen eşleştirme formlarına dayanır. Daha önce belirtildiği gibi, bu türlerin tasarımının gelişme olasılığı varsa somut veri gösterimlerini ortaya çıkarmaktan kaçınmalısınız.
Örneğin, ayrımcı bir birleşimin gösterimi, özel veya iç bildirim kullanılarak veya imza dosyası kullanılarak gizlenebilir.
type Union =
private
| CaseA of int
| CaseB of string
Seçici bir biçimde yapılmış birleşimleri gelişi güzel ortaya çıkartırsanız, kullanıcı kodunu bozmadan kitaplığınızı yeni bir sürüme geçirirken zorlanabilirsiniz. Bunun yerine, türünüzün değerleri üzerinde desen eşleştirmeye izin vermek için bir veya daha fazla etkin desen göstermeyi göz önünde bulundurun.
Etkin desenler, F# Birleşim Türlerini doğrudan ortaya çıkarmaktan kaçınırken F# tüketicilerine desen eşleştirmesi sağlamak için alternatif bir yol sağlar.
Satır içi İşlevler ve Üye Kısıtlamaları
Zımni üye kısıtlamaları ve statik olarak çözümlenen genel türler içeren satır içi işlevleri kullanarak genel sayısal algoritmaları tanımlama
Aritmetik üye kısıtlamaları ve F# karşılaştırma kısıtlamaları F# programlama için bir standarttır. Örneğin, aşağıdaki kodu göz önünde bulundurun:
let inline highestCommonFactor a b =
let rec loop a b =
if a = LanguagePrimitives.GenericZero<_> then b
elif a < b then loop a (b - a)
else loop (a - b) b
loop a b
Bu işlevin türü aşağıdaki gibidir:
val inline highestCommonFactor : ^T -> ^T -> ^T
when ^T : (static member Zero : ^T)
and ^T : (static member ( - ) : ^T * ^T -> ^T)
and ^T : equality
and ^T : comparison
Bu, matematiksel kitaplıktaki genel API için uygun bir işlevdir.
Tür sınıflarını taklit etmek ve ördek tipi kullanmak için üye kısıtlamaları kullanmaktan kaçının
F# üye kısıtlamalarını kullanarak "ördek yazma" benzetimi yapmak mümkündür. Ancak, bunu kullanan üyeler genel olarak F#-F# kitaplık tasarımlarında kullanılmamalıdır. Bunun nedeni, tanıdık olmayan veya standart dışı örtük kısıtlamalara dayalı kitaplık tasarımlarının kullanıcı kodunun esnek olmayan ve belirli bir çerçeve düzenine bağlanmasına neden olma eğiliminde olmasıdır.
Buna ek olarak, üye kısıtlamalarının bu şekilde yoğun kullanımı çok uzun derleme sürelerine neden olabilir.
İşleç Tanımları
Özel sembolik işleçleri tanımlamaktan kaçının
Özel operatörler bazı durumlarda temel öneme sahiptir ve geniş bir uygulama kodu kümesi içinde son derece yararlı gösterimsel araçlardır. Kitaplığın yeni kullanıcıları için adlandırılmış işlevlerin kullanımı genellikle daha kolaydır. Buna ek olarak, özel sembolik işleçleri belgelemesi zor olabilir ve kullanıcılar IDE ve arama motorlarındaki mevcut sınırlamalar nedeniyle işleçler hakkında yardım aramayı daha zor bulur.
Sonuç olarak, işlevinizi adlandırılmış işlevler ve üyeler olarak yayımlamak ve ayrıca yalnızca notasyonsal avantajlar belgelerden ve bunlara sahip olmanın bilişsel maliyetinden daha fazlaysa işleçleri bu işlev için kullanıma sunmanız en iyisidir.
Ölçü Birimleri
F# kodunda ek tür güvenliği için ölçü birimlerini dikkatle kullanın
Ölçü birimleri için ek yazma bilgileri, diğer .NET dilleri tarafından görüntülendiğinde silinir. .NET bileşenlerinin, araçlarının ve yansımalarının birimler olmadan türleri göreceğini unutmayın. Örneğin, C# tüketicileri floatyerine float<kg> görür.
Tür Kısaltmaları
F# kodunu basitleştirmek için tür kısaltmalarını dikkatle kullanın
.NET bileşenleri, araçları ve yansıma türleri için kısaltılmış adları görmez. Tür kısaltmalarının önemli ölçüde kullanımı, bir etki alanının aslında olduğundan daha karmaşık görünmesini sağlayabilir ve bu da tüketicilerin kafasını karıştırabilir.
Üyeleri ve özellikleri doğası gereği kısaltılan türdeki seçeneklerden farklı olması gereken genel türler için tür kısaltmalarından kaçının.
Bu durumda kısaltılan tür, tanımlanan gerçek türün gösterimi hakkında çok fazla şey gösterir. Bunun yerine, kısaltmayı bir sınıf türü ya da tek durumluluk ayrımlı birleşim içinde sarmanız önerilir (veya performansın kritik olduğu durumlarda, kısaltmayı sarmak için bir yapı türü (struct) kullanmayı düşünebilirsiniz).
Örneğin, çok eşlemeli bir haritayı F# eşlemesinin özel bir örneği olarak tanımlamak caziptir, örneğin:
type MultiMap<'Key,'Value> = Map<'Key,'Value list>
Ancak, bu türdeki mantıksal nokta gösterimi işlemleri bir Haritadaki işlemlerle aynı değildir; örneğin, arama operatörü map[key], anahtar sözlükte yoksa istisna oluşturmak yerine boş listeyi döndürmesi mantıklıdır.
Diğer .NET Dillerinden Kullanılacak Kitaplıklar için Yönergeler
Diğer .NET dillerinden kullanılacak kitaplıklar tasarlarken, .NET Kitaplığı Tasarım Yönergeleri'ne uymak önemlidir. Bu belgede, bu kitaplıklar kısıtlama olmadan F# yapılarını kullanan F#'a yönelik kitaplıkların aksine vanilya .NET kitaplıkları olarak etiketlenmiştir. Vanilya .NET kitaplıkları tasarlamak, genel API'de F#'ye özgü yapıların kullanımını en aza indirerek .NET Framework'ün geriye kalanıyla tutarlı, tanıdık ve dile özgü API'ler sağlamak anlamına gelir. Kurallar aşağıdaki bölümlerde açıklanmıştır.
Ad alanı tasarımı ve Tür tasarımı (diğer .NET Dillerinden kullanılacak kitaplıklar için)
Bileşenlerinizin genel API'sine .NET adlandırma kurallarını uygulama
Kısaltılmış adların ve .NET büyük harf kullanımı yönergelerinin kullanımına özellikle dikkat edin.
type pCoord = ...
member this.theta = ...
type PolarCoordinate = ...
member this.Theta = ...
Bileşenleriniz için birincil kuruluş yapısı olarak ad alanlarını, türleri ve üyeleri kullanın
Genel işlevler içeren tüm dosyalar namespace bildirimiyle başlamalıdır ve ad alanları içindeki yalnızca genel kullanıma yönelik varlıklar türler olmalıdır. F# modüllerini kullanmayın.
Uygulama kodunu, yardımcı program türlerini ve yardımcı program işlevlerini tutmak için genel olmayan modülleri kullanın.
Statik türler, API'nin gelecekte aşırı yüklenmesini ve F# modüllerinde kullanılamayabilecek diğer .NET API tasarım kavramlarını kullanmasına olanak tanıyacak şekilde modüller yerine tercih edilmelidir.
Örneğin, aşağıdaki genel API yerine:
module Fabrikam
module Utilities =
let Name = "Bob"
let Add2 x y = x + y
let Add3 x y z = x + y + z
Bunun yerine göz önünde bulundurun:
namespace Fabrikam
[<AbstractClass; Sealed>]
type Utilities =
static member Name = "Bob"
static member Add(x,y) = x + y
static member Add(x,y,z) = x + y + z
Türlerin tasarımı değişmiyorsa vanilya .NET API'lerinde F# kayıt türlerini kullanma
F# kayıt türleri basit bir .NET sınıfına derlenmiştir. Bunlar API'lerdeki bazı basit ve kararlı türler için uygundur. Arabirimlerin otomatik olarak oluşturulmasını engellemek için [<NoEquality>] ve [<NoComparison>] özniteliklerini kullanmayı göz önünde bulundurun. Ayrıca, genel bir alanı kullanıma sunan vanilya .NET API'lerinde değiştirilebilir kayıt alanlarını kullanmaktan kaçının. Bir sınıfın API'nin gelecekteki gelişimi için daha esnek bir seçenek sağlayıp sağlamayacağını her zaman göz önünde bulundurun.
Örneğin, aşağıdaki F# kodu genel API'yi bir C# tüketicisinin kullanıma sunar:
F#:
[<NoEquality; NoComparison>]
type MyRecord =
{ FirstThing: int
SecondThing: string }
C#:
public sealed class MyRecord
{
public MyRecord(int firstThing, string secondThing);
public int FirstThing { get; }
public string SecondThing { get; }
}
Vanilya .NET API'lerinde F# birleşim türlerinin gösterimini gizleme
F# birleşim türleri, F#-F# kodlaması için bile bileşen sınırları boyunca yaygın olarak kullanılmaz. Bunlar, bileşenler ve kitaplıklar içinde dahili olarak kullanıldığında mükemmel bir uygulama cihazıdır.
Vanilya .NET API'si tasarlarken, özel bildirim veya imza dosyası kullanarak birleşim türünün gösterimini gizlemeyi göz önünde bulundurun.
type PropLogic =
private
| And of PropLogic * PropLogic
| Not of PropLogic
| True
Ayrıca, istenen bir .NET ile uyumlu API sağlamak amacıyla üyelerle birlikte bir birleşim temsili kullanan türleri de artırabilirsiniz.
type PropLogic =
private
| And of PropLogic * PropLogic
| Not of PropLogic
| True
/// A public member for use from C#
member x.Evaluate =
match x with
| And(a,b) -> a.Evaluate && b.Evaluate
| Not a -> not a.Evaluate
| True -> true
/// A public member for use from C#
static member CreateAnd(a,b) = And(a,b)
Çerçevenin tasarım desenlerini kullanarak GUI ve diğer bileşenleri tasarlama
.NET'in içinde WinForms, WPF ve ASP.NET gibi birçok farklı çerçeve vardır. Bu çerçevelerde kullanılacak bileşenler tasarlarsanız, her birine yönelik adlandırma ve tasarım kuralları kullanılmalıdır. Örneğin, WPF programlama için, tasarladığınız sınıflar için WPF tasarım desenlerini benimseyin. Kullanıcı arabirimi programlama modellerinde olaylar gibi tasarım desenlerini ve System.Collections.ObjectModeliçinde bulunanlar gibi bildirim tabanlı koleksiyonları kullanın.
Diğer .NET dillerinden kullanılmak üzere kütüphaneler için nesne ve üye tasarımını düzenleme
.NET olaylarını kullanıma açmak için CLIEvent özniteliğini kullanma
Diğer .NET dillerine aşina oldukları şekilde olayların yayımlanabilmesi için, nesne ve DelegateEvent alan belirli bir .NET temsilci türüyle EventArgs oluşturun (bu da varsayılan olarak yalnızca Event türünü kullanan FSharpHandleryerine).
type MyBadType() =
let myEv = new Event<int>()
[<CLIEvent>]
member this.MyEvent = myEv.Publish
type MyEventArgs(x: int) =
inherit System.EventArgs()
member this.X = x
/// A type in a component designed for use from other .NET languages
type MyGoodType() =
let myEv = new DelegateEvent<EventHandler<MyEventArgs>>()
[<CLIEvent>]
member this.MyEvent = myEv.Publish
Zaman uyumsuz işlemleri .NET görevleri döndüren yöntemler olarak kullanıma sunma
Görevler etkin zaman uyumsuz hesaplamaları temsil etmek için .NET'te kullanılır. Görevler genellikle F# Async<T> nesnelerine kıyasla daha az bileşim özelliğine sahiptir, çünkü "zaten yürütülmekte olan" görevleri temsil ederler ve paralel bileşim gerçekleştirecek şekilde veya iptal sinyallerinin ve diğer bağlamsal parametrelerin yayılmasını gizleyen şekillerde bir araya getirilemezler.
Ancak, buna rağmen, Görevleri döndüren yöntemler .NET'te zaman uyumsuz programlamanın standart gösterimidir.
/// A type in a component designed for use from other .NET languages
type MyType() =
let compute (x: int): Async<int> = async { ... }
member this.ComputeAsync(x) = compute x |> Async.StartAsTask
Ayrıca genellikle açık bir iptal belirtecini de kabul etmek istersiniz:
/// A type in a component designed for use from other .NET languages
type MyType() =
let compute(x: int): Async<int> = async { ... }
member this.ComputeAsTask(x, cancellationToken) = Async.StartAsTask(compute x, cancellationToken)
F# işlev türleri yerine .NET temsilci türlerini kullanma
Burada "F# işlev türleri" int -> intgibi "ok" türleri anlamına gelir.
Bunun yerine:
member this.Transform(f: int->int) =
...
Bunu yapın:
member this.Transform(f: Func<int,int>) =
...
F# işlev türü diğer .NET dillerine class FSharpFunc<T,U> olarak görünür ve temsilci türlerini anlayan dil özellikleri ve araçları için daha az uygundur. .NET Framework 3.5 veya üzerini hedefleyen daha yüksek dereceli bir yöntem yazarken, System.Func ve System.Action temsilcileri .NET geliştiricilerinin bu API'leri sorunsuz bir şekilde kullanabilmesi için yayımlanması gereken doğru API'lerdir. (.NET Framework 2.0'ı hedeflerken, sistem tanımlı temsilci türleri daha sınırlıdır; System.Converter<T,U> gibi önceden tanımlanmış temsilci türlerini kullanmayı veya belirli bir temsilci türünü tanımlamayı göz önünde bulundurun.)
Diğer taraftan, .NET delege, F#'ye yönelik kütüphaneler için doğal değildir (bkz. F#'ye yönelik kütüphanelerinde sonraki Bölüm). Sonuç olarak, temel .NET kütüphaneleri için üst düzey yöntemler geliştirirken yaygın bir uygulama stratejisi, tüm uygulamayı F# işlev türlerini kullanarak yazmak ve ardından gerçek F# uygulamasının üzerinde ince bir cephe olarak temsilciler kullanarak genel API'yi oluşturmak şeklindedir.
F# seçenek değerlerini döndürmek yerine TryGetValue desenini kullanın ve F# seçenek değerlerini argüman olarak almak yerine yöntemin aşırı yüklenmesini tercih edin.
API'lerdeki F# seçenek türü için yaygın kullanım desenleri, standart .NET tasarım teknikleri kullanılarak vanilya .NET API'lerinde daha iyi uygulanır. F# seçenek değeri döndürmek yerine bool dönüş türünü ve "TryGetValue" deseninde olduğu gibi bir out parametresi kullanmayı göz önünde bulundurun. F# seçenek değerlerini parametre olarak kullanmak yerine yöntemi aşırı yükleme veya isteğe bağlı bağımsız değişkenler kullanmayı göz önünde bulundurun.
member this.ReturnOption() = Some 3
member this.ReturnBoolAndOut(outVal: byref<int>) =
outVal <- 3
true
member this.ParamOption(x: int, y: int option) =
match y with
| Some y2 -> x + y2
| None -> x
member this.ParamOverload(x: int) = x
member this.ParamOverload(x: int, y: int) = x + y
Parametreler ve dönüş değerleri için IEnumerable<T> ve IDictionary<Key,Value> arabirim türlerini kullanın.
.NET dizileri T[], list<T>F# türleri, Map<Key,Value> ve Set<T>gibi somut koleksiyon türlerinin ve Dictionary<Key,Value>gibi .NET somut koleksiyon türlerinin kullanılmasından kaçının. .NET Kitaplığı Tasarım Yönergeleri, IEnumerable<T>gibi çeşitli koleksiyon türlerinin ne zaman kullanılacağıyla ilgili iyi önerilere sahiptir. Dizilerin (T[]) bazı kullanımları bazı durumlarda performans gerekçesiyle kabul edilebilir. Özellikle seq<T> yalnızca IEnumerable<T>için F# diğer adı olduğunu ve bu nedenle seq'nin genellikle vanilya .NET API'sine uygun bir tür olduğunu unutmayın.
F# listeleri yerine:
member this.PrintNames(names: string list) =
...
F# dizilerini kullanın:
member this.PrintNames(names: seq<string>) =
...
Sıfır bağımsız değişkenli bir yöntemi tanımlamak için bir yöntemin tek giriş türü olarak veya void-return yöntemini tanımlamak için tek dönüş türü olarak birim türünü kullanın
Birim türünün diğer kullanımlarından kaçının. Bunlar iyi:
✔ member this.NoArguments() = 3
✔ member this.ReturnVoid(x: int) = ()
Bu kötü bir durumdur:
member this.WrongUnit( x: unit, z: int) = ((), ())
Vanilya .NET API sınırlarında null değerleri denetleyin
Sabit tasarım desenleri ve F# türleri için null değişmez değerlerin kullanımına yönelik kısıtlamalar nedeniyle F# uygulama kodu daha az null değere sahip olma eğilimindedir. Diğer .NET dilleri genellikle null değerini çok daha sık bir değer olarak kullanır. Bu nedenle, bir vanilya .NET API'sini kullanıma veren F# kodu, API sınırında null için parametreleri denetlemeli ve bu değerlerin F# uygulama koduna daha derin akmasını engellemelidir.
isNull deseninde null işlevi veya desen eşleştirmesi kullanılabilir.
let checkNonNull argName (arg: obj) =
match arg with
| null -> nullArg argName
| _ -> ()
let checkNonNull' argName (arg: obj) =
if isNull arg then nullArg argName
else ()
F# 9'dan başlayarak, derleyicinin olası null değerleri ve işlenmesi gereken yerleri belirtmesini sağlamak için yeni | nullsöz dizimi kullanabilirsiniz:
let checkNonNull argName (arg: obj | null) =
match arg with
| null -> nullArg argName
| _ -> ()
let checkNonNull' argName (arg: obj | null) =
if isNull arg then nullArg argName
else ()
F# 9'da, derleyici olası bir null değerin işlenmediğini algıladığında bir uyarı yayar:
let printLineLength (s: string) =
printfn "%i" s.Length
let readLineFromStream (sr: System.IO.StreamReader) =
// `ReadLine` may return null here - when the stream is finished
let line = sr.ReadLine()
// nullness warning: The types 'string' and 'string | null'
// do not have equivalent nullability
printLineLength line
Bu uyarılar, F# null deseni eşleme sırasında kullanılarak ele alınmalıdır.
let printLineLength (s: string) =
printfn "%i" s.Length
let readLineFromStream (sr: System.IO.StreamReader) =
let line = sr.ReadLine()
match line with
| null -> ()
| s -> printLineLength s
Tuple'ları dönüş değerleri olarak kullanmaktan kaçının
Bunun yerine, toplama verilerini tutan adlandırılmış bir tür döndürmeyi veya birden çok değer döndürmek için out parametreleri kullanmayı tercih edin. Tanımlama kümeleri ve yapı tanımlama kümeleri .NET'te (yapı tanımlama kümeleri için C# dil desteği dahil) mevcut olsa da, çoğunlukla .NET geliştiricileri için ideal ve beklenen API'yi sağlamaz.
Parametrelerin currying tekniği kullanımından kaçının
Bunun yerine Method(arg1,arg2,…,argN).NET çağırma kurallarını kullanın.
member this.TupledArguments(str, num) = String.replicate num str
İpucu: Kitaplıkları herhangi bir .NET dilinden kullanılmak üzere tasarlıyorsanız, kitaplıklarınızın bu dillerden "doğru" hissettirdiğinden emin olmak için deneysel C# ve Visual Basic çalışması yapmanın yerini hiçbir şey tutamaz. Kitaplıkların ve belgelerinin geliştiricilere beklendiği gibi göründüğünden emin olmak için .NET Reflector ve Visual Studio Nesne Tarayıcısı gibi araçları da kullanabilirsiniz.
Ekler
F# kodunu diğer .NET dilleri tarafından kullanılacak şekilde tasarlamanın uçtan uca örneği
Aşağıdaki sınıfı göz önünde bulundurun:
open System
type Point1(angle,radius) =
new() = Point1(angle=0.0, radius=0.0)
member x.Angle = angle
member x.Radius = radius
member x.Stretch(l) = Point1(angle=x.Angle, radius=x.Radius * l)
member x.Warp(f) = Point1(angle=f(x.Angle), radius=x.Radius)
static member Circle(n) =
[ for i in 1..n -> Point1(angle=2.0*Math.PI/float(n), radius=1.0) ]
Bu sınıfın çıkarımlı F# türü aşağıdaki gibidir:
type Point1 =
new : unit -> Point1
new : angle:double * radius:double -> Point1
static member Circle : n:int -> Point1 list
member Stretch : l:double -> Point1
member Warp : f:(double -> double) -> Point1
member Angle : double
member Radius : double
Şimdi bu F# türünün başka bir .NET dili kullanan bir programcıya nasıl göründüğüne göz atalım. Örneğin, yaklaşık C# "imzası" aşağıdaki gibidir:
// C# signature for the unadjusted Point1 class
public class Point1
{
public Point1();
public Point1(double angle, double radius);
public static Microsoft.FSharp.Collections.List<Point1> Circle(int count);
public Point1 Stretch(double factor);
public Point1 Warp(Microsoft.FSharp.Core.FastFunc<double,double> transform);
public double Angle { get; }
public double Radius { get; }
}
F# öğesinin burada yapıları nasıl temsil ettiği konusunda dikkat edilmesi gereken bazı önemli noktalar vardır. Mesela:
Bağımsız değişken adları gibi meta veriler korundu.
İki bağımsız değişken alan F# yöntemleri, iki bağımsız değişken alan C# yöntemleri haline gelir.
İşlevler ve listeler, F# kitaplığındaki ilgili türlere başvurular haline gelir.
Aşağıdaki kodda bu kodun bu öğeleri hesaba katacak şekilde nasıl ayarlayabileceğiniz gösterilmektedir.
namespace SuperDuperFSharpLibrary.Types
type RadialPoint(angle:double, radius:double) =
/// Return a point at the origin
new() = RadialPoint(angle=0.0, radius=0.0)
/// The angle to the point, from the x-axis
member x.Angle = angle
/// The distance to the point, from the origin
member x.Radius = radius
/// Return a new point, with radius multiplied by the given factor
member x.Stretch(factor) =
RadialPoint(angle=angle, radius=radius * factor)
/// Return a new point, with angle transformed by the function
member x.Warp(transform:Func<_,_>) =
RadialPoint(angle=transform.Invoke angle, radius=radius)
/// Return a sequence of points describing an approximate circle using
/// the given count of points
static member Circle(count) =
seq { for i in 1..count ->
RadialPoint(angle=2.0*Math.PI/float(count), radius=1.0) }
Kodun çıkarsanan F# türü aşağıdaki gibidir:
type RadialPoint =
new : unit -> RadialPoint
new : angle:double * radius:double -> RadialPoint
static member Circle : count:int -> seq<RadialPoint>
member Stretch : factor:double -> RadialPoint
member Warp : transform:System.Func<double,double> -> RadialPoint
member Angle : double
member Radius : double
C# imzası şu şekildedir:
public class RadialPoint
{
public RadialPoint();
public RadialPoint(double angle, double radius);
public static System.Collections.Generic.IEnumerable<RadialPoint> Circle(int count);
public RadialPoint Stretch(double factor);
public RadialPoint Warp(System.Func<double,double> transform);
public double Angle { get; }
public double Radius { get; }
}
Bu türün standart bir .NET kitaplığının parçası olarak kullanılması için yapılan düzeltmeler aşağıdaki gibidir:
Çeşitli adlar düzeltildi:
Point1,n,lvefsırasıylaRadialPoint,count,factorvetransformoldu.seq<RadialPoint>kullanılarak yapılan bir liste derlemesiniRadialPoint listkullanılarak sıralı bir yapıya değiştirip,[ ... ]yerineIEnumerable<RadialPoint>dönüş türünü kullandı.F# işlev türü yerine .NET temsilci türünü
System.Funckullandı.
Bu, C# kodunda kullanılmasını çok daha iyi hale getirir.