F # ile bir kütüphane tasarlamaya çalışıyorum. Kitaplık, hem F # hem de C # için uygun olmalıdır .
Ve burası biraz sıkıştığım yer. Bunu F # dostu yapabilirim veya C # dostu yapabilirim, ancak sorun her ikisi için de nasıl kolay hale getirileceğidir.
İşte bir örnek. F # 'da aşağıdaki işleve sahip olduğumu hayal edin:
let compose (f: 'T -> 'TResult) (a : 'TResult -> unit) = f >> a
Bu, F # 'dan mükemmel şekilde kullanılabilir:
let useComposeInFsharp() =
let composite = compose (fun item -> item.ToString) (fun item -> printfn "%A" item)
composite "foo"
composite "bar"
C # 'da compose
işlev aşağıdaki imzaya sahiptir:
FSharpFunc<T, Unit> compose<T, TResult>(FSharpFunc<T, TResult> f, FSharpFunc<TResult, Unit> a);
Ama elbette FSharpFunc
imzayı istemiyorum, istediğim şey şu Func
ve Action
bunun yerine:
Action<T> compose2<T, TResult>(Func<T, TResult> f, Action<TResult> a);
Bunu başarmak için şöyle bir compose2
işlev oluşturabilirim :
let compose2 (f: Func<'T, 'TResult>) (a : Action<'TResult> ) =
new Action<'T>(f.Invoke >> a.Invoke)
Şimdi, bu C # 'da mükemmel şekilde kullanılabilir:
void UseCompose2FromCs()
{
compose2((string s) => s.ToUpper(), Console.WriteLine);
}
Ama şimdi compose2
F # kullanarak bir sorunumuz var ! Şimdi tüm standart F # sarmak zorunda funs
içine Func
ve Action
bunun gibi,:
let useCompose2InFsharp() =
let f = Func<_,_>(fun item -> item.ToString())
let a = Action<_>(fun item -> printfn "%A" item)
let composite2 = compose2 f a
composite2.Invoke "foo"
composite2.Invoke "bar"
Soru: Hem F # hem de C # kullanıcıları için F # ile yazılmış kitaplık için birinci sınıf deneyimi nasıl elde edebiliriz?
Şimdiye kadar, bu iki yaklaşımdan daha iyisini bulamadım:
- İki ayrı derleme: biri F # kullanıcılarını ve ikincisi C # kullanıcılarını hedefliyor.
- Bir derleme ancak farklı ad alanları: biri F # kullanıcıları için ve ikincisi C # kullanıcıları için.
İlk yaklaşım için şöyle bir şey yapardım:
Bir F # projesi oluşturun, buna FooBarFs adını verin ve FooBarFs.dll'de derleyin.
- Kitaplığı tamamen F # kullanıcılarına hedefleyin.
- .Fsi dosyalarından gereksiz her şeyi gizleyin.
Başka bir F # projesi oluşturun, FooBarCs'yi arayın ve FooFar.dll'de derleyin
- Kaynak düzeyinde ilk F # projesini yeniden kullanın.
- Bu projedeki her şeyi gizleyen .fsi dosyası oluşturun.
- Ad, ad alanları vb. İçin C # deyimlerini kullanarak kitaplığı C # şeklinde ortaya çıkaran .fsi dosyası oluşturun.
- Çekirdek kitaplığa yetki veren sarmalayıcılar oluşturun ve gerektiğinde dönüşümü gerçekleştirin.
Ad alanlarıyla ilgili ikinci yaklaşımın kullanıcılar için kafa karıştırıcı olabileceğini düşünüyorum, ancak o zaman bir derlemeniz var.
Soru: Bunların hiçbiri ideal değil, belki bir tür derleyici bayrağı / anahtarı / özniteliği veya bir tür hile özlüyorum ve bunu yapmanın daha iyi bir yolu var mı?
Soru: başka biri benzer bir şeyi başarmaya çalıştı mı ve öyleyse bunu nasıl yaptınız?
DÜZENLEME: Açıklığa kavuşturmak gerekirse, soru yalnızca işlevler ve temsilcilerle ilgili değil, aynı zamanda bir F # kitaplığına sahip bir C # kullanıcısının genel deneyimiyle ilgili. Bu, ad alanlarını, adlandırma kurallarını, deyimleri ve C # için yerel olan benzerlerini içerir. Temel olarak, bir C # kullanıcısı kitaplığın F # ile yazıldığını algılayamamalıdır. Ve bunun tersi, bir F # kullanıcısı bir C # kitaplığıyla uğraşıyormuş gibi hissetmelidir.
DÜZENLEME 2:
Şimdiye kadarki cevaplardan ve yorumlardan, sorumun gerekli derinliğe sahip olmadığını görebiliyorum, belki de çoğunlukla F # ve C # arasındaki birlikte çalışabilirlik sorunlarının ortaya çıktığı tek bir örnek, fonksiyon değerleri sorunu nedeniyle. Bence bu en bariz örnek ve bu yüzden bu beni soruyu sormak için kullanmamı sağladı, ancak aynı şekilde ilgilendiğim tek sorunun bu olduğu izlenimini verdi.
Daha somut örnekler vereyim. En mükemmel F # Bileşen Tasarım Yönergeleri belgesini okudum (bunun için çok teşekkürler @gradbot!). Belgedeki yönergeler, kullanılıyorsa, bazı konuları ele alır, ancak hepsini değil.
Belge iki ana bölüme ayrılmıştır: 1) F # kullanıcılarını hedeflemeye yönelik yönergeler; ve 2) C # kullanıcılarını hedeflemeye yönelik yönergeler. Hiçbir yerde tek tip bir yaklaşıma sahip olmanın mümkün olduğunu iddia etmeye bile kalkışmıyor, bu da sorumu tam olarak yansıtıyor: F # hedefleyebiliriz, C # hedefleyebiliriz, ancak her ikisini de hedeflemenin pratik çözümü nedir?
Hatırlatmak gerekirse, amaç F # dilinde yazılmış ve hem F # hem de C # dillerinden deyimsel olarak kullanılabilen bir kitaplığa sahip olmaktır .
Buradaki anahtar kelime deyimseldir . Sorun, kütüphaneleri farklı dillerde kullanmanın sadece mümkün olduğu genel birlikte çalışabilirlik değildir.
Şimdi doğrudan F # Bileşen Tasarım Kılavuzlarından aldığım örneklere gelelim .
Modüller + işlevler (F #) - Ad Alanları + Türler + işlevler
F #: Türlerinizi ve modüllerinizi içermek için ad alanları veya modüller kullanın. Deyimsel kullanım, işlevleri modüllere yerleştirmektir, örneğin:
// library module Foo let bar() = ... let zoo() = ... // Use from F# open Foo bar() zoo()
C #: Vanilya .NET API'leri için bileşenleriniz için (modüllerin aksine) birincil organizasyon yapısı olarak ad alanlarını, türleri ve üyeleri kullanın.
Bu, F # yönergesi ile uyumsuzdur ve örneğin C # kullanıcılarına uyması için yeniden yazılması gerekir:
[<AbstractClass; Sealed>] type Foo = static member bar() = ... static member zoo() = ...
Yine de bunu yaparak, deyimsel kullanımı F # 'dan ayırırız çünkü artık kullanamayız
bar
vezoo
önek koymadan kullanamayızFoo
.
Tuple kullanımı
F #: Dönüş değerleri için uygun olduğunda tuple kullanın.
C #: Vanilya .NET API'lerinde tupleları dönüş değerleri olarak kullanmaktan kaçının.
zaman uyumsuz
F #: F # API sınırlarında zaman uyumsuz programlama için Async kullanın.
C #: Zaman uyumsuz işlemleri, F # Async nesneleri yerine .NET eşzamansız programlama modelini (BeginFoo, EndFoo) veya .NET görevlerini (Task) döndüren yöntemler olarak kullanarak açığa çıkarın.
Kullanımı
Option
F #: İstisnaları artırmak yerine dönüş türleri için seçenek değerlerini kullanmayı düşünün (F # -facing kodu için).
Vanilya .NET API'lerinde F # seçenek değerleri (seçenek) döndürmek yerine TryGetValue modelini kullanmayı düşünün ve F # seçenek değerlerini bağımsız değişken olarak almak yerine yöntem aşırı yüklemesini tercih edin.
Ayrımcı sendikalar
F #: Ağaç yapılı veriler oluşturmak için sınıf hiyerarşilerine alternatif olarak ayırt edici birleşimleri kullanın
C #: Bunun için belirli bir yönerge yok, ancak ayrımcılığa uğramış sendikalar kavramı C # için yabancı
Körili fonksiyonlar
F #: curried işlevler F # için deyimseldir
C #: Vanilya .NET API'lerinde parametrelerin karıştırılmasını kullanmayın.
Boş değerler kontrol ediliyor
F #: Bu, F # için deyimsel değildir
C #: Vanilya .NET API sınırlarında boş değerleri kontrol etmeyi düşünün.
F # tiplerinin kullanılması
list
,map
,set
vbF #: Bunları F # dilinde kullanmak deyimseldir
C #: Vanilla .NET API'lerinde parametreler ve dönüş değerleri için IEnumerable ve IDictionary .NET koleksiyonu arabirim türlerini kullanmayı düşünün. ( Yani F # kullanmayın
list
,map
,set
)
İşlev türleri (bariz olan)
F #: Değerler olarak F # işlevlerinin kullanılması F # için deyimseldir, tabii ki
C #: Vanilya .NET API'lerinde F # işlev türleri yerine .NET temsilci türlerini kullanın.
Sorumun doğasını göstermek için bunların yeterli olması gerektiğini düşünüyorum.
Bu arada, kılavuzların da kısmi bir cevabı var:
... vanilya .NET kitaplıkları için daha yüksek sıralı 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 temsilcileri kullanarak genel API'yi oluşturmaktır.
Özetle.
Kesin bir cevap var: Kaçırdığım hiçbir derleyici numarası yok .
Yönergeler belgesine göre, önce F # için yazma ve ardından .NET için bir cephe sarmalayıcı oluşturmanın makul bir strateji olduğu görülmektedir.
O zaman soru, bunun pratik uygulamasıyla ilgili kalır:
Ayrı montajlar mı? veya
Farklı ad alanları?
Yorumum doğruysa, Tomas ayrı ad alanları kullanmanın yeterli ve kabul edilebilir bir çözüm olması gerektiğini öne sürüyor.
Sanırım ad alanlarının seçiminin .NET / C # kullanıcılarını şaşırtmayacak veya şaşırtmayacak şekilde olduğu göz önüne alındığında, bu da onlar için ad alanının muhtemelen onlar için birincil ad alanı gibi görünmesi gerektiği anlamına gelir. F # kullanıcıları, F # 'ye özgü ad alanını seçme yükünü üstleneceklerdir. Örneğin:
FSharp.Foo.Bar -> kitaplığa bakan F # için ad alanı
Foo.Bar -> .NET sarmalayıcı için ad alanı, C # için deyimsel