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

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

问题描述

我正在尝试用 F# 设计一个库.该库应该适合F# 和 C# 使用.

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

这就是我被卡住的地方.我可以使它对 F# 友好,也可以使它对 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.

这是一个例子.想象一下,我在 F# 中有以下函数:

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

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

这在 F# 中完全可用:

This is perfectly usable from F#:

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

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

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

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

当然,我不想在签名中使用 FSharpFunc,我想要的是 FuncAction,就像这样:

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);

为了实现这一点,我可以像这样创建 compose2 函数:

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)

现在,这在 C# 中完全可用:

Now, this is perfectly usable in C#:

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

但是现在我们在使用 F# 中的 compose2 时遇到了问题!现在我必须将所有标准的 F# funs 包装到 FuncAction 中,如下所示:

But now we have a 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"

问题:对于用 F# 编写的库,我们如何才能为 F# 和 C# 用户实现一流的体验?

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. 两个独立的程序集:一个面向 F# 用户,另一个面向 C# 用户.
  2. 一个程序集,但命名空间不同:一个用于 F# 用户,第二个用于 C# 用户.

对于第一种方法,我会这样做:

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

  1. 创建一个 F# 项目,将其命名为 FooBarFs 并将其编译为 FooBarFs.dll.

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

  • 该库完全面向 F# 用户.
  • 隐藏 .fsi 文件中不需要的所有内容.

创建另一个 F# 项目,调用 if FooBarCs 并将其编译为 FooFar.dll

Create another F# project, call if FooBarCs and compile it into FooFar.dll

  • 在源代码级别重用第一个 F# 项目.
  • 创建 .fsi 文件以隐藏该项目的所有内容.
  • 创建 .fsi 文件,以 C# 方式公开库,使用 C# 习语作为名称、命名空间等.
  • 创建委托给核心库的包装器,在必要时进行转换.

我认为使用命名空间的第二种方法可能会让用户感到困惑,但是您只有一个程序集.

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/attribute 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?

澄清一下,问题不仅与函数和委托有关,而且与使用 F# 库的 C# 用户的整体体验有关.这包括 C# 原生的命名空间、命名约定、习惯用法等.基本上,C# 用户不应该能够检测到该库是用 F# 编写的.反之亦然,F# 用户应该感觉像是在处理 C# 库.

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.

编辑 2:

从目前的回答和评论中可以看出,我的问题缺乏必要的深度,可能主要是因为仅使用了一个示例,其中 F# 和 C# 之间存在互操作性问题出现,函数值的问题.我认为这是最明显的例子,所以这个导致我用它来问这个问题,但同样地给人的印象是这是我唯一关心的问题.

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 function 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.

让我提供更具体的例子.我已经阅读了最优秀的F# 组件设计指南文档(非常感谢@gradbot!).文档中的指南,如果使用,请解决一些问题,但不是全部.

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.

该文档分为两个主要部分:1) 针对 F# 用户的指南;2) 指导方针面向 C# 用户.它甚至没有试图假装可以拥有制服方法,正好呼应了我的问题:我们可以针对 F#,也可以针对 C#,但是什么是针对两者的实用解决方案?

The document is split into 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?

提醒一下,我们的目标是拥有一个用 F# 编写的库,并且可以惯用从F# 和 C# 语言.

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.

现在来看我直接从的例子F# 组件设计指南.

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

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

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

  • F#:务必使用命名空间或模块来包含您的类型和模块.惯用的用法是在模块中放置函数,例如:

  • 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#:务必使用命名空间、类型和成员作为您的主要组织结构组件(相对于模块),用于 vanilla .NET API.

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

    这与 F# 指南不兼容,并且该示例需要重新编写以适合 C# 用户:

    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() = ...
    

    通过这样做,我们打破了 F# 的惯用用法,因为我们不能再使用 barzoo 而不用 Foo 作为前缀.

    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.

    元组的使用

    • F#:在适合返回值时使用元组.

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

    C#:避免在 vanilla .NET API 中使用元组作为返回值.

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

    异步

    • F#:务必在 F# API 边界使用 Async 进行异步编程.

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

    C#:使用 .NET 异步编程模型公开异步操作(BeginFoo, EndFoo),或作为方法返回 .NET 任务 (Task),而不是作为 F# Async对象.

    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.

    Option

    • F#:考虑对返回类型使用选项值而不是引发异常(对于面向 F# 的代码).

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

    考虑使用 TryGetValue 模式,而不是在 vanilla 中返回 F# 选项值(选项).NET API,更喜欢方法重载而不是将 F# 选项值作为参数.

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

    受歧视的工会

    • F#:务必使用可区分联合作为类层次结构的替代方法来创建树结构数据

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

    C#:没有具体的指导方针,但歧视联合的概念对 C# 来说是陌生的

    柯里化函数

    • F#:柯里化函数是 F# 惯用的

    C#:不要在 vanilla .NET API 中使用柯里化参数.

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

    检查空值

    • F#:这不是 F# 的惯用语

    C#:考虑在 vanilla .NET API 边界上检查空值.

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

    使用F#类型listmapset

    • F#:在 F# 中使用这些是惯用的

    C#:考虑使用 .NET 集合接口类型 IEnumerable 和 IDictionary用于 vanilla .NET API 中的参数和返回值.(即不要使用 F#listmapset)

    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)

    函数类型(显而易见的)

    Function types (the obvious one)

    • F#:使用 F# 函数作为值是 F# 的惯用方式,显然

    C#:在 vanilla .NET API 中优先使用 .NET 委托类型而不是 F# 函数类型.

    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 a partial answer:

    ... 开发高阶时常用的实现策略vanilla .NET 库的方法是使用 F# 函数类型编写所有实现,并且然后使用委托创建公共 API 作为实际 F# 实现之上的薄外观.

    ... 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.

    总结.

    有一个明确的答案:我没有遗漏任何编译器技巧.

    根据指南文档,似乎首先为 F# 创作然后创建.NET 的外观包装器是一个合理的策略.

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

    问题仍然是关于此的实际实施:

    The question then remains regarding the practical implementation of this:

    • 单独的程序集?或

    • Separate assemblies? or

    不同的命名空间?

    如果我的解释是正确的,Tomas 建议使用单独的命名空间应该足够了,并且应该是可接受的解决方案.

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

    我想我会同意这一点,因为命名空间的选择是这样的不会让 .NET/C# 用户感到惊讶或混淆,这意味着命名空间对于他们来说,它可能看起来像是他们的主要命名空间.这F# 用户将不得不承担选择特定于 F# 的命名空间的负担.例如:

    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 -> 面向库的 F# 命名空间

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

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

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

    推荐答案

    Daniel 已经解释了如何定义您编写的 F# 函数的 C# 友好版本,因此我将添加一些更高级别的注释.首先,您应该阅读 F# 组件设计指南(已被 gradbot 引用).这是一份解释如何使用 F# 设计 F# 和 .NET 库的文档,它应该可以回答您的许多问题.

    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.

    在使用 F# 时,基本上可以编写两种库:

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

    • F# 库 旨在从 F# 中使用,因此它的公共接口是以函数式风格编写的(使用 F# 函数类型、元组、区分工会等)

    • 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 库 旨在用于任何 .NET 语言(包括 C# 和 F#),它通常遵循 .NET 面向对象的风格.这意味着您将大部分功能公开为带有方法的类(有时是扩展方法或静态方法,但大部分代码应在 OO 设计中编写).

    .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).

    在您的问题中,您询问如何将函数组合公开为 .NET 库,但我认为从 .NET 库的角度来看,像您的 compose 这样的函数是太低级的概念.您可以将它们公开为使用 FuncAction 的方法,但这可能不是您首先设计普通 .NET 库的方式(也许您会改用 Builder 模式或类似的东西).

    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).

    在某些情况下(即在设计与 .NET 库风格不太匹配的数字库时),设计一个混合了 F# 的库是很有意义的.NET 样式在单个库中.最好的方法是使用普通的 F#(或普通的 .NET)API,然后提供包装器以在其他风格中自然使用.包装器可以位于单独的命名空间中(例如 MyLibrary.FSharpMyLibrary).

    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).

    在您的示例中,您可以将 F# 实现保留在 MyLibrary.FSharp 中,然后在 MyLibrary 命名空间作为某个类的静态方法.但同样,.NET 库可能具有比函数组合更具体的 API.

    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# 和 C# 的 F# 库的最佳方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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