我想在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