generics - F# 中有没有办法编写一个可以接受 Tuple 或 Single 值的通用函数?

标签 generics f# tuples operator-overloading

我正在编写一些代码来处理大型数据文件。如果该字段为 none,则 CSV TypeProvider 将为给定值返回 NaN。我有很多字段,所以如果我编写一个辅助函数来检查那个 NaN 并返回一个 None 会更干净。不知道如何编写更通用的辅助函数,我想出了这个:

let (!?) x = if Double.IsNaN(x) then None else Some (decimal x)
let (!?) (x, y) = 
    match (x, y) with
    | (x, y) when not(Double.IsNaN(x)) && not (Double.IsNaN(y))  -> Some (decimal x, decimal y)
    | (_, _) -> None

不幸的是,我对运算符重载的尝试无法正常工作,并且复制代码也不是很好。一个新手问,有没有更好的方法来做到这一点?

(我知道 PreferOptionals 之类的东西,但我需要更有选择性地这样做)

最佳答案

您需要将它们设为中间类型的静态成员:

open System

type T = T with
    static member ($) (T, x) = if Double.IsNaN(x) then None else Some (decimal x)
    static member ($) (T,(x, y)) = 
        match (x, y) with
        | (x, y) when not(Double.IsNaN(x)) && not (Double.IsNaN(y))  -> Some (decimal x, decimal y)
        | (_, _) -> None

let inline parse x = T $ x


// Usage

let a = parse (1. , 2.)
// val a : (decimal * decimal) option = Some (1M, 2M)

let b = parse 1.
// val b : decimal option = Some 1M

还要注意,最好使用二元运算符来发送中间类型。这样可以延迟重载解决。

您也可以使用命名函数,但它更冗长。

编辑关于延迟重载解决。

首先,我说过要将它写成命名函数,你必须手动编写约束:

let inline parse (x: ^a) =
    ((^b or ^a) : (static member ($) : ^b -> ^a -> _) T,x)

请注意,后来添加的另一个答案与此完全相同,唯一的区别是它使用名称 ToOption 作为静态成员而不是运算符。

好的,现在让我们尝试摆脱T,我们可以在一定程度上做到这一点:

type T = T with
    static member ($) x = if Double.IsNaN(x) then None else Some (decimal x)
    static member ($) ((x, y)) = 
        match (x, y) with
        | (x, y) when not(Double.IsNaN(x)) && not (Double.IsNaN(y))  -> Some (decimal x, decimal y)
        | (_, _) -> None

let inline parse (x: ^a) =
    let call (_:'b) = ((^b or ^a) : (static member ($) : ^a -> _) x)
    call T

请注意,我必须创建一种将 ^bT 统一的方法,这就是我添加 call 函数的原因。

现在这仍然有效,它是一种有效的解决方案,可以清除重载签名并在解析函数中添加更多噪声,在许多情况下这是一个很好的权衡。但是,如果我从 trait 调用中完全删除 ^a 参数怎么办?

let inline parse (x: ^a) =
    let call (_:'b) = (^b : (static member ($) : ^a -> _) x)
    call T

这失败了

~vs6086.fsx(11,27): error FS0043: A unique overload for method 'op_Dollar' could not be determined based on type information prior to this program point. A type annotation may be needed.

Known return type: 'b

Known type parameter: <  ^a >

Candidates:
 - static member T.( $ ) : (float * float) -> (decimal * decimal) option
 - static member T.( $ ) : x:float -> decimal option

这是为什么呢?

因为现在 F# 编译器在特征调用站点知道所有涉及的类型变量。

在最后一次更改之前,^a 未知,^bT 统一,但由于另一个未知,因此重载解析被延迟到 parse 函数的每个 future 单独调用,这是可行的,因为它被声明为内联。

通过了解所有类型参数,它尝试应用标准的 .Net 重载解决方案,但由于有 2 个候选者而失败。

希望这个解释有意义。

关于generics - F# 中有没有办法编写一个可以接受 Tuple 或 Single 值的通用函数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60963773/

相关文章:

java - 定制表的通用设计

delphi - delphi 中的 std::multimap 等效项

f# - 是否可以在 F# 中使用正常顺序评估

c# - 在单元测试中适当使用 Tuple<>?

F# : How to provide fst for tuples, 三倍和四倍而不牺牲运行时性能

java - 通用扩展和实现

Scala 更高级的类型 : can't define a generic function that works on different collections

f# - 为什么我不能在 F# 中使用最新版本的 NUnit 和 FsCheck?

f# - 为什么使用向后管道运算符可以解决编译错误?

python - TypeError : tuple indices must be integers