我想在F#中設計一個庫。該圖書館應友好使用從都F#和C#。設計F#庫以供F#和C#使用的最佳方法
這就是我卡住了一點點的地方。我可以讓F#友好,或者我可以讓C#友好,但問題是如何使它對兩者都友好。
這裏是一個例子。想象一下,我在F#以下功能:
let compose (f: 'T -> 'TResult) (a : 'TResult -> unit) = f >> a
這是F#非常有用的:
let useComposeInFsharp() =
let composite = compose (fun item -> item.ToString) (fun item -> printfn "%A" item)
composite "foo"
composite "bar"
在C#中,compose
功能具有以下特徵:
FSharpFunc<T, Unit> compose<T, TResult>(FSharpFunc<T, TResult> f, FSharpFunc<TResult, Unit> a);
,但當然我不想在簽名FSharpFunc
,我想要的是Func
和Action
而不是,如下所示:
Action<T> compose2<T, TResult>(Func<T, TResult> f, Action<TResult> a);
要做到這一點,我可以創造compose2
功能是這樣的:
let compose2 (f: Func<'T, 'TResult>) (a : Action<'TResult>) =
new Action<'T>(f.Invoke >> a.Invoke)
現在,這是在C#中非常有用的:
void UseCompose2FromCs()
{
compose2((string s) => s.ToUpper(), Console.WriteLine);
}
但現在我們已經使用compose2
從F#的問題!現在,我必須包裝所有標準的F#funs
到Func
和Action
,像這樣:
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"
問題:我們怎樣才能實現寫在F#兩個F#和C#用戶庫一流的經驗嗎?
到目前爲止,我不能想出什麼比這兩種方法更好:
- 兩個獨立的組件:一個針對F#的用戶,第二個爲C#用戶。
- 一個程序集,但名稱空間不同:一個用於F#用戶,另一個用於C#用戶。
對於第一種方法,我會做這樣的事情:
創建F#項目,稱之爲FooBarFs並把它編譯成FooBarFs.dll。
- 僅將目標庫定位到F#用戶。
- 從.fsi文件中隱藏不必要的東西。
創建另一個F#項目,調用if FooBarCs並將其編譯到FooFar中。dll
- 在源代碼級別重新使用第一個F#項目。
- 創建隱藏該項目所有內容的.fsi文件。
- 創建使用C#方式公開庫的.fsi文件,使用C#成語來表示名稱,名稱空間等。
- 創建委託給核心庫的包裝,在必要時進行轉換。
我認爲與命名空間的第二種方法可以迷惑用戶,但你有一個程序集。
問題:這些都不是理想的,也許我缺少某種編譯器標誌/開關/ attribte 或某種把戲並沒有這樣做的更好的辦法?
問題:有其他人試圖實現類似的東西,如果是的話你是怎麼做到的?
編輯:澄清,這個問題不僅是關於函數和代表,而是一個C#用戶的F#庫的總體經驗。這包括C#原生的命名空間,命名約定,習慣用語等。基本上,C#用戶不應該能夠檢測到該庫是在F#中編寫的。反之亦然,F#用戶應該像處理C#庫一樣。
編輯2:
我可以從答案和註釋中看到,到目前爲止,我的問題缺乏必要的深度, 也許主要是由於只使用一個例子,其中F#之間的互操作性問題, C# 出現了,函數的問題出現了一個值。我認爲這是最明顯的例子,所以這 使我用它來問這個問題,但同樣的道理給我的印象是,這是我所關心的唯一問題 。
讓我提供更具體的例子。我已閱讀了最優秀的 F# Component Design Guidelines 文檔(非常感謝@gradbot!)。如果使用了文檔中的指導原則,則可以解決 中的一些問題,但不是全部。
該文件分爲兩個主要部分:1)針對F#用戶的指導原則;和2)針對C#用戶的 指南。它甚至沒有試圖假裝可以有統一的 方法,這正好迴應了我的問題:我們可以定位F#,我們可以定位C#,但是針對兩者的實用解決方案是什麼?
提醒,目標是在F#中創建一個庫,並且可以使用慣用的從 F#和C#語言。
這裏的關鍵字是慣用的。這個問題不是一般的互操作性,只是可能的 使用不同語言的庫。
現在來看我從 F# Component Design Guidelines直接得到的例子。
模塊+功能(F#)與命名空間+類型+功能
F#:不要使用命名空間或模塊包含您的類型和模塊。 的習慣用法是把功能模塊,例如:
// library module Foo let bar() = ... let zoo() = ... // Use from F# open Foo bar() zoo()
C#:執行使用名稱空間,類型和成員作爲主要的組織結構爲您 部件(而不是模塊),香草.NET蜜蜂。
這與F#方針是不相容的,該示例將需要 重新編寫以適應C#用戶:
[<AbstractClass; Sealed>] type Foo = static member bar() = ... static member zoo() = ...
通過這樣做雖然,我們打破F#的習慣用法,因爲 我們不能再使用
bar
和zoo
,而不用在Foo
前添加它。
使用的元組
F#的:你在適當的時候對返回值使用的元組。
C#:避免在香草.NET API中使用元組作爲返回值。
異步
F#:不要使用異步在F#API邊界異步編程。
C#:不要使用暴露無論是.NET異步編程模型 (BeginFoo,EndFoo)異步操作,或爲返回.NET任務的方法(任務),而不是F#異步 對象。
使用
Option
F#:考慮使用選項值返回類型,而不是引發異常(用於F#-facing代碼)。
考慮使用TryGetValue模式,而不是在香草 .NET API中返回F#選項值(選項),並且首選方法重載以將F#選項值作爲參數。
識別聯合
F#:不要使用可識別聯合作爲替代類層次結構用於創建樹結構數據
C#:對此沒有具體的指導方針,但歧視聯盟的概念對於C而言是外國的#
咖喱功能
F#:咖喱功能是地道的F#
C#:不要使用的香草.NET API的參數鑽營。
檢查NULL值
F#:這不是地道的F#
C#:考慮檢查香草.NET API邊界空值。
使用F#類型
list
,map
,set
等F#:是慣用使用這些在F#
C#:使用.NET考慮收集界面類型IEnumerable和IDictionary ,以獲取vanilla .NET API中的參數和返回值。 (即不使用F#
list
,map
,set
)
函數類型(明顯的一個)
F#:使用的F#的功能值是慣用爲F#,有意識地
C#:使用.NET委託類型優先於在香草.NET API中的F#函數類型。
我想這些應該足以證明我的問題的性質。
順便說一句,這些準則也有部分答案:
...共同執行戰略發展高階 方法香草.NET庫是使用F#功能類型來編寫所有的實現時,和 然後使用委託創建公共API作爲實際F#實現上的薄外觀。
總結。
有一個確定的答案:沒有編譯器技巧,我錯過了。
根據指導文檔,似乎首先編寫F#然後創建一個.NET外觀包裝是合理的策略。
的問題然後保持關於切實落實這一點:
獨立的組件?或
不同的命名空間?
如果我的解釋是正確的,托馬斯認爲,使用不同的命名空間應該 足夠了,而應該是一個可以接受的解決方案。
我想我會用,鑑於命名空間的選擇是這樣的,它 不驚或混淆的.NET/C#用戶,這意味着它們的命名空間 也許應該像它同意是主他們的名字空間。 F#用戶將不得不承擔選擇特定於F#的名稱空間的負擔。 例如:
FSharp.Foo.Bar - 面向> F#的命名空間庫
Foo.Bar - >命名空間爲.NET包裝,慣用的C#
有點相關http://research.microsoft.com/en-us/um/cambridge/projects/fsharp/manual/fsharp-component-design-guidelines.pdf – gradbot 2012-04-11 16:43:17
除了傳遞一流的功能,什麼是你的擔憂? F#已經在通過其他語言提供無縫體驗方面邁出了很大的步伐。 – Daniel 2012-04-11 17:12:26
回覆:您的編輯,我還不清楚爲什麼您認爲在F#中編寫的圖書館在C#中顯示爲非標準。大多數情況下,F#提供了C#功能的超集。讓圖書館感覺自然很大程度上是一種慣例,無論您使用何種語言,都是如此。所有這一切,F#提供了你需要做的所有工具。 – Daniel 2012-04-11 21:18:57