gpt4 book ai didi

f# - F# lambda vs Func 的重载解析

转载 作者:行者123 更新时间:2023-12-04 06:31:07 26 4
gpt4 key购买 nike

我正在向这样的记录类型添加静态构建器方法:

type ThingConfig = { url: string; token : string; } with
static member FromSettings (getSetting : (string -> string)) : ThingConfig =
{
url = getSetting "apiUrl";
token = getSetting "apiToken";
}

我可以这样称呼它:
let config = ThingConfig.FromSettings mySettingsAccessor

现在是棘手的部分:我想添加第二个重载构建器以供 C# 使用(暂时忽略重复的实现):
static member FromSettings (getSetting : System.Func<string,string>) : ThingConfig =
{
url = getSetting.Invoke "apiUrl";
token = getSetting.Invoke "apiToken";
}

这适用于 C#,但会破坏我之前的 F# 调用
错误 FS0041:无法根据此程序点之前的类型信息确定方法“FromSettings”的唯一重载。可能需要类型注释。候选:静态成员 ThingConfig.FromSettings : getSetting:(string -> string) -> ThingConfig,静态成员 ThingConfig.FromSettings : getSetting:Func -> ThingConfig

为什么 F# 不能确定调用哪一个?

该类型注释会是什么样子? (我可以从调用站点注释参数类型吗?)

这种互操作有更好的模式吗? (重载接受来自 C# 和 F# 的 lambdas)

最佳答案

为什么 F# 不能确定调用哪一个?

F# 中的重载分辨率通常比 C# 更受限制。为了安全起见,F# 编译器通常会拒绝 C# 编译器认为有效的重载。

然而,这个具体案例是真正的模棱两可。为了 .NET 互操作,F# 编译器对 lambda 表达式有一个特殊规定:通常,lambda 表达式将被编译为 F# 函数,但如果已知预期类型为 Func<_,_> ,编译器会将 lambda 转换为 .NET 委托(delegate)。这允许我们使用基于高阶函数的 .NET API,例如 IEnumerable<_> (又名 LINQ),无需手动转换每个 lambda。

所以在你的情况下,编译器真的很困惑:你是想将 lambda 表达式保留为 F# 函数并调用你的 F# 重载,还是要将其转换为 Func<_,_>并调用 C# 重载?

类型注释会是什么样子?

为了帮助编译器,您可以将 lambda 表达式的类型显式声明为 string -> string ,像这样:

let cfg = ThingConfig.FromSettings( (fun s -> foo) : string -> string )

更好的方法是在 FromSettings 之外定义函数。称呼:
let getSetting s = foo
let cfg = ThingConfig.FromSettings( getSetting )

这很好用,因为自动转换为 Func<_,_>仅适用于内联编写的 lambda 表达式。编译器不会将任何函数转换为 .NET 委托(delegate)。因此,声明 getSetting FromSettings 之外call 明确其类型 string -> string ,并且重载决议有效。

EDIT: it turns out that the above no longer actually works. The current F# compiler will convert any function to a .NET delegate automatically, so even specifying the type as string -> string doesn't remove the ambiguity. Read on for other options.



说到类型注释 - 您可以以类似的方式选择其他重载:
let cfg = ThingConfig.FromSettings( (fun s -> foo) : Func<_,_> )

或使用 Func构造函数:
let cfg = ThingConfig.FromSettings( Func<_,_>(fun s -> foo) )

在这两种情况下,编译器都知道参数的类型是 Func<_,_>。 ,所以可以选择重载。

有更好的模式吗?

过载通常是不好的。它们在某种程度上掩盖了正在发生的事情,使程序更难调试。我已经失去了 C# 重载解析选择 IEnumerable 的错误数。而不是 IQueryable ,从而将整个数据库拉到 .NET 端。

在这些情况下我通常会做什么,我声明两个不同名称的方法,然后使用 CompiledNameAttribute从 C# 中查看时为它们提供替代名称。例如:
type ThingConfig = ...

[<CompiledName "FromSettingsFSharp">]
static member FromSettings (getSetting : (string -> string)) = ...

[<CompiledName "FromSettings">]
static member FromSettingsCSharp (getSetting : Func<string, string>) = ...

这样,F# 代码将看到两个方法, FromSettingsFromSettingsCSharp , 而 C# 代码将看到相同的两个方法,但命名为 FromSettingsFSharpFromSettings分别。智能感知体验会有点难看(但很容易理解!),但完成的代码在两种语言中看起来完全一样。

更简单的选择:惯用的命名

在 F# 中,将函数命名为第一个字符小写是惯用的。有关示例,请参见标准库 - Seq.empty , String.concat等。因此,在您的情况下,我实际上会做什么,我将创建两种方法,一种用于 F#,名为 fromSettings ,另一个用于 C#,名为 FromSettings :
type ThingConfig = ...

static member fromSettings (getSetting : string -> string) =
...

static member FromSettings (getSetting : Func<string,string>) =
ThingConfig.fromSettings getSetting.Invoke

(另请注意,第二种方法可以根据第一种方法实现;您不必复制和粘贴实现)

关于f# - F# lambda vs Func 的重载解析,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49164915/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com