设计F#库使用来自F#和C#最好的方法 [英] Best approach for designing F# libraries for use from both F# and C#

查看:126
本文介绍了设计F#库使用来自F#和C#最好的方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图在设计F#库。图书馆应是友好的使用从的两个F#和C#



而这正是我坚持一点点。我可以使它F#友好的,或者我可以让C#友好的,但问题是如何使它友好的两个。



下面是一个例子。想象一下,我在F#以下功能:



<预类=郎毫升prettyprint-覆盖> 让我们谱写(F:'T - > 'TResult)(A:'TResult - >单位)= F>>一个

这是从F#非常有用的:



<前类=郎毫升prettyprint-覆盖> 让useComposeInFsharp()=
让复合=组成(有趣的项目 - > item.ToString)(有趣的项目 - > printfn% A项)
复合富
复合栏中的

在C#中,撰写函数具有以下签名:



<预类=郎-CS prettyprint-覆盖> FSharpFunc< T,单元>撰写< T,TResult>(FSharpFunc< T,TResult> F,FSharpFunc< TResult,单元> A);



但我当然不希望 FSharpFunc 在签名,我要的是函数功能动作来代替,就像这样:



<预类=郎-CS prettyprint-覆盖> 动作< T> compose2< T,TResult>(Func键< T,TResult> F,动作< TResult>一种);

要实现这一点,我可以创建 compose2 像这样的功能:



<预类=郎毫升prettyprint-覆盖> 让compose2(F:FUNC<'T'TResult>)(答:动作<'TResult>)=
新动作<'T>(f.Invoke>> a.Invoke)

现在,这是在C#中非常有用的:



<预类=郎-CS prettyprint-覆盖> 无效UseCompose2FromCs()
{
compose2((字符串s)=> s.ToUpper(),Console.WriteLine);
}



但是,现在我们有使用问题 compose2 从F#!现在,我必须包装所有标准的F#玩意儿函数功能动作,就像这样:



<预类=郎毫升prettyprint-覆盖> 让useCompose2InFsharp()=
设F = Func键< ; _,_>(有趣的项目 - > item.ToString())
让=动作< _>(有趣的项目 - > printfn%A项)
让composite2 = compose2发

composite2.Invoke富
composite2.Invoke栏中的

的问题:如何才能实现写在F#为F#和C#用户库



所以,一流的经验吗?到目前为止,我不能想出什么比这两种方法更好:




  1. 两个单独的组件:一个定位到F#用户和第二个C#用户

  2. 一个组件,但不同的命名空间:。一个用于F#的用户,第二个为C#用户



对于第一种方式,我会做这样的事情:




  1. 创建F#项目,称它为FooBarFs并把它编译成FooBarFs.dll。




    • 纯目标库,F#的用户。

    • 隐藏一切从.fsi文件不必要的。


  2. 创建另一个F#项目中,调用如果FooBarCs,并把它编译成FooFar.dll




    • 重用在源代码级别的第一个F#项目。

    • 创建.fsi文件,其中隐藏的一切从该项目。

    • 创建它暴露在C#的方式库.fsi文件,使用C#成语的名字,命名空间等。

    • 创建封套授人以核心库,做转换必要。




我觉得与命名空间第二种方法可能会造成混淆的用户,但你有一个装配。



的问题:这些都不是理想的,也许我缺少一些编译器标志/开关/ attribte
或一些样的招并没有这样做的更好的方法。



的问题:有其他人试图实现类似的东西,如果又如何你做它



编辑:澄清,这个问题不仅是功能和代表,但与F#库中的C#用户的整体体验。这包括命名空间,命名约定,成语等类似原产于C#。基本上,一个C#用户不应该能够检测到文库中的F#撰写。 。反之亦然,一个F#用户应该觉得自己像处理一个C#库






编辑2:



我可以从答案和注释中看到,到目前为止,我的问题缺乏必要的深度,
也许主要是由于只使用一个例子,其中的互操作性F#和C#
之间出现问题时,一个函数值的问题。我觉得这是最明显的例子,所以这
促使我用它来问这个问题,但同样给人的印象是,这是
我关心的唯一问题。



让我提供更多具体的例子。我已经度过了最优秀的
F#组件设计准则
文档(非常感谢@gradbot这个!)。在文件中的指导方针,如果使用的话,做地址
部分的问题,但不是所有的



该文件被分割它两个主要部分:1)指南F#的用户定位; 2)为
指南针对C#的用户。无处它甚至试图假装它是不可能有一个统一的
方法,这正好呼应了我的问题:我们可以针对F#中,我们可以针对C#的,但究竟是什么目标都
实用的解决方案?



要提醒的是,我们的目标是拥有一个图书馆在F#编写的,并且可以使用的惯用从
两个F#
的和C#语言。



这里的关键词是的习惯的。这个问题是不是一般的互操作性,这只是可能
使用不同的语言库。



现在的例子,我采取直接从
F#组件设计准则




  1. 模块+功能(F#)与命名空间+类型+功能




    • F#:不要使用命名空间或模块包含你的类型和模块。
      中的习惯用法是将功能模块,如:

        //库
      模块美孚
      让巴()= ...
      让动物园()= ...


      //从F#
      打开美孚
      使用巴()
      动物园()


    • C#:不要使用命名空间,类型和成员作为你
      分量的主要的组织结构(相对于模块),香草.NET的API。



      这是与F#准则不相容,和这个例子就需要
      重新编写以适应C#用户:

        [<抽象类;密封>] 
      型美孚=
      静态杆件()= ...
      静态成员动物园()= ...

      这样做虽然,我们打破F#的习惯用法,因为
      ,我们可以不再使用动物园没有与富。



  2. 元组的使用




    • F#:不要使用元组在适当的时候对于返回值


    • C#。使用元组作为返回值香草.NET API的避免



  3. 异步




    • F#:不要使用异步的异步编程在F#API边界。


    • C#:不要使用任何的.NET异步编程模型
      (BeginFoo,EndFoo)揭露异步操作,或方法返回.NET任务(任务),而不是像F#异步
      的对象。



  4. 使用选项




    • F#:考虑使用选项值返回类型,而不是引发异常(为F#-facing代码)。


    • 考虑使用,而不是香草
      .NET API的返回F#选项值(可选)的TryGetValue模式,更喜欢方法重载采取F#选项值作为参数。<​​/ p>



  5. 识别联合




    • F#:不要使用可识别联合作为替代类层次结构用于创建树结构数据


    • C#的这个没有具体的指导方针,但受歧视工会的概念是陌生的C#



  6. 咖喱功能




    • F#的咖喱功能是地道的F#


    • C#:不要在香草.NET API使用的参数钻营



  7. 检查空值




    • F#的这不是地道对于F#


    • C#:考虑检查香草.NET API边界空值



  8. 使用F#类型的列表地图设置




    • F#的这是地道的F#


    • C#来使用这些:考虑使用.NET集合的接口类型IEnumerable和IDictionary的
      的参数和返回值在香草.NET的API。 (即不使用F#列表地图设置 的)



  9. 函数类型(明显的一个)




    • F#的使用F#函数作为值是地道的F#,obiously


    • C#:不要使用.NET委托类型优先于F#的功能类型的香草.NET的API





我想这些应该足以证明我的问题的性质。



顺便说一下,指导方针也有部分答案:




开发香草.NET库高阶
方法时,

...共同实施策略是使用F#功能类型来编写所有的实施,
然后创建使用委托作为薄的外观实际F#实现上盖的公共API。




来概括。



有1明确的答案:还有,我错过了



按照指引文档,似乎创作了F#,然后再没有编译器技巧。在创建
A门面的.NET包装是合理的策略



接下来的问题就仍然是实际执行这样的:




  • 单独的程序集?或


  • 不同的命名空间?




如果我的解释是正确的,托马斯认为,使用不同的命名空间应该
是足够的,而应该是一个可以接受的解决办法。



我想我会用,鉴于达成一致命名空间的选择是这样的,它
不惊或混淆的.NET / C#用户,这意味着该命名空间
他们也许应该像它是他们的主要空间。在
F#用户将不得不采取选择F#特异性命名空间的负担。
例如:




  • FSharp.Foo.Bar - >命名空间,F#面临库


  • Foo.Bar - >命名空间.NET包装,惯用的C#



解决方案

丹尼尔已经解释如何定义你写的F#函数的C# - 友好的版本,所以我会添加一些更高层次的意见。首先,你应该阅读 F#组件设计准则(引用已经gradbot)。这是一个文件,说明如何使用F#来设计F#和.NET库,它应该回答你的很多问题。



在使用F#中,有基本上有两种;库,你可以这样写:




  • F#库以使用设计的唯一从F#,所以它的公共接口是用一个函数式风格(使用F#函数类型,元组,歧视工会等)


  • 。 NET库的设计是从的​​任何的.NET语言(包括C#和F#),它通常遵循.NET的面向对象式的使用。这意味着你暴露的大部分功能与方法的类(有时扩展方法或静态方法,但大多代码应该于二OO设计写)。




在你的问题,你问如何公开函数组合作为.NET库,但我觉得喜欢你的撰写从的角度.NET库点太低层次的概念。你可以将它们公开为与函数功能动作工作方法,但是这可能不是你会如何设计一个正常摆在首位.NET库(也许你会使用Builder模式,而不是或类似的东西)。



在某些情况下(即设计数字库,别当真正的.NET库风格合身),它使一个良好的意识,设计出两种混合图书馆F# .NET 样式单个库中。要做到这一点的最好办法是有正常的F#(或正常.NET)API,然后在其他样式自然​​的使用提供包装。包装器可以在不同的命名空间(如 MyLibrary.FSharp 在MyLibrary )。



在你的榜样,你可以离开 MyLibrary.FSharp F#的实现,然后添加.NET(C# - 型)包装(在在MyLibrary 命名空间的某个类的静态方法类似的代码,丹尼尔帐)。但同样,.NET库可能会比函数组合更具体的API。


I am trying to design a library in F#. The library should be friendly for use from both F# and C#.

And this is where I'm stuck a little bit. I can make it F# friendly, or I can make it C# friendly, but the problem is how to make it friendly for both.

Here is an example. Imagine I have the following function in F#:

let compose (f: 'T -> 'TResult) (a : 'TResult -> unit) = f >> a

This is perfectly usable from F#:

let useComposeInFsharp() =
    let composite = compose (fun item -> item.ToString) (fun item -> printfn "%A" item)
    composite "foo"
    composite "bar"

In C#, the compose function has the following signature:

FSharpFunc<T, Unit> compose<T, TResult>(FSharpFunc<T, TResult> f, FSharpFunc<TResult, Unit> a);

But of course I don't want FSharpFunc in the signature, what I want is Func and Action instead, like this:

Action<T> compose2<T, TResult>(Func<T, TResult> f, Action<TResult> a);

To achieve this, I can create compose2 function like this:

let compose2 (f: Func<'T, 'TResult>) (a : Action<'TResult> ) = 
    new Action<'T>(f.Invoke >> a.Invoke)

Now this is perfectly usable in C#:

void UseCompose2FromCs()
{
    compose2((string s) => s.ToUpper(), Console.WriteLine);
}

But now we have problem using compose2 from F#! Now I have to wrap all standard F# funs into Func and Action, like this:

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"

The question: How can we achieve first-class experience for the library written in F# for both F# and C# users?

So far, I couldn't come up with anything better than these two approaches:

  1. Two separate assemblies: one targeted to F# users, and the second to C# users.
  2. One assembly but different namespaces: one for F# users, and the second for C# users.

For the first approach, I would do something like this:

  1. Create F# project, call it FooBarFs and compile it into FooBarFs.dll.

    • Target the library purely to F# users.
    • Hide everything unnecessary from the .fsi files.
  2. Create another F# project, call if FooBarCs and compile it into FooFar.dll

    • Reuse the first F# project at the source level.
    • Create .fsi file which hides everything from that project.
    • Create .fsi file which exposes the library in C# way, using C# idioms for name, namespaces etc.
    • Create wrappers that delegate to the core library, doing the conversion where necessary.

I think the second approach with the namespaces can be confusing to the users, but then you have one assembly.

The question: none of these are ideal, perhaps I am missing some kind of compiler flag/switch/attribte or some kind of trick and there is a better way of doing this?

The question: has anyone else tried to achieve something similar and if so how did you do it?

EDIT: to clarify, the question is not only about functions and delegates but the overall experience of a C# user with an F# library. This includes namespaces, naming conventions, idioms and suchlike that are native to C#. Basically, a C# user shouldn't be able to detect that the library was authored in F#. And vice versa, an F# user should feel like dealing with a C# library.


EDIT 2:

I can see from the answers and comments so far that my question lacks the necessary depth, perhaps mostly due to use of only one example where interoperability issues between F# and C# arise, the issue of functions a values. I think this is the most obvious example and so this led me to use it to ask the question, but by the same token gave the impression that this is the only issue I am concerned with.

Let me provide more concrete examples. I have read through the most excellent F# Component Design Guidelines document (many thanks @gradbot for this!). The guidelines in the document, if used, do address some of the issues but not all.

The document is split it two main parts: 1) guidelines for targeting F# users; and 2) guidelines for targeting C# users. Nowhere does it even attempt to pretend that it is possible to have a uniform approach, which exactly echoes my question: we can target F#, we can target C#, but what is the practical solution for targeting both?

To remind, the goal is to have a library authored in F#, and which can be used idiomatically from both F# and C# languages.

The keyword here is idiomatic. The issue is not the general interoperability where it is just possible to use libraries in different languages.

Now to the examples, which I take straight from F# Component Design Guidelines.

  1. Modules+functions (F#) vs Namespaces+Types+functions

    • F#: Do use namespaces or modules to contain your types and modules. The idiomatic use is to place functions in modules, e.g.:

      // library
      module Foo
      let bar() = ...
      let zoo() = ...
      
      
      // Use from F#
      open Foo
      bar()
      zoo()
      

    • C#: Do use namespaces, types and members as the primary organizational structure for your components (as opposed to modules), for vanilla .NET APIs.

      This is incompatible with the F# guideline, and the example would need to be re-written to fit the C# users:

      [<AbstractClass; Sealed>]
      type Foo =
          static member bar() = ...
          static member zoo() = ...
      

      By doing so though, we break the idiomatic use from F# because we can no longer use bar and zoo without prefixing it with Foo.

  2. Use of tuples

    • F#: Do use tuples when appropriate for return values.

    • C#: Avoid using tuples as return values in vanilla .NET APIs.

  3. Async

    • F#: Do use Async for async programming at F# API boundaries.

    • C#: Do expose asynchronous operations using either the .NET asynchronous programming model (BeginFoo, EndFoo), or as methods returning .NET tasks (Task), rather than as F# Async objects.

  4. Use of Option

    • F#: Consider using option values for return types instead of raising exceptions (for F#-facing code).

    • Consider use the TryGetValue pattern instead of returning F# option values (option) in vanilla .NET APIs, and prefer method overloading to taking F# option values as arguments.

  5. Discriminated unions

    • F#: Do use discriminated unions as an alternative to class hierarchies for creating tree-structured data

    • C#: no specific guidelines for this, but the concept of discriminated unions is foreign to C#

  6. Curried functions

    • F#: curried functions are idiomatic for F#

    • C#: Do not use currying of parameters in vanilla .NET APIs.

  7. Checking for null values

    • F#: this is not idiomatic for F#

    • C#: Consider checking for null values on vanilla .NET API boundaries.

  8. Use of F# types list, map, set, etc

    • F#: it is idiomatic to use these in F#

    • C#: Consider using the .NET collection interface types IEnumerable and IDictionary for parameters and return values in vanilla .NET APIs. (i.e. do not use F# list, map, set)

  9. Function types (the obvious one)

    • F#: use of F# functions as values is idiomatic for F#, obiously

    • C#: Do use .NET delegate types in preference to F# function types in vanilla .NET APIs.

I think these should be sufficient to demonstrate the nature of my question.

Incidentally, the guidelines also have partial answer:

... a common implementation strategy when developing higher-order methods for vanilla .NET libraries is to author all the implementation using F# function types, and then create the public API using delegates as a thin façade atop the actual F# implementation.

To summarise.

There is one definite answer: there are no compiler tricks that I missed.

As per the guidelines doc, it seems that authoring for F# first and then creating a facade wrapper for .NET is the reasonable strategy.

The question then remains regarding the practical implementation of this:

  • Separate assemblies? or

  • Different namespaces?

If my interpretation is correct, Tomas suggests that using separate namespaces should be sufficient, and should be an acceptable solution.

I think I will agree with that given that the choice of namespaces is such that it does not surprise or confuse the .NET/C# users, which means that the namespace for them should probably look like it is the primary namespace for them. The F# users will have to take the burden of choosing F#-specific namespace. For example:

  • FSharp.Foo.Bar -> namespace for F# facing library

  • Foo.Bar -> namespace for .NET wrapper, idiomatic for C#

解决方案

Daniel already explained how to define a C#-friendly version of the F# function that you wrote, so I'll add some higher-level comments. First of all, you should read the F# Component Design Guidelines (referenced already by gradbot). This is a document that explains how to design F# and .NET libraries using F# and it should answer many of your questions.

When using F#, there are basically two kinds of libraries you can write:

  • F# library is designed to be used only from F#, so it's public interface is written in a functional style (using F# function types, tuples, discriminated unions etc.)

  • .NET library is designed to be used from any .NET language (including C# and F#) and it typically follows .NET object-oriented style. This means that you'll expose most of the functionality as classes with method (and sometimes extension methods or static methods, but mostly the code should be written in the OO design).

In your question, you're asking how to expose function composition as a .NET library, but I think that functions like your compose are too low level concepts from the .NET library point of view. You can expose them as methods working with Func and Action, but that probably isn't how you would design a normal .NET library in the first place (perhaps you'd use the Builder pattern instead or something like that).

In some cases (i.e. when designing numerical libraries that do not really fit well with the .NET library style), it makes a good sense to design a library that mixes both F# and .NET styles in a single library. The best way to do this is to have normal F# (or normal .NET) API and then provide wrappers for natural use in the other style. The wrappers can be in a separate namespace (like MyLibrary.FSharp and MyLibrary).

In your example, you could leave the F# implementation in MyLibrary.FSharp and then add .NET (C#-friendly) wrappers (similar to code that Daniel posted) in the MyLibrary namespace as static method of some class. But again, .NET library would probably have more specific API than function composition.

这篇关于设计F#库使用来自F#和C#最好的方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆