Generics 在F#中有没有一种方法可以编写一个可以接受元组或单个值的泛型函数?

Generics 在F#中有没有一种方法可以编写一个可以接受元组或单个值的泛型函数?,generics,f#,tuples,operator-overloading,Generics,F#,Tuples,Operator Overloading,我正在写一些代码来处理一个大数据文件。如果字段为none,CSV TypeProvider将返回给定值的NaN。我有很多字段,所以如果我编写一个helper函数来检查NaN并返回一个None,会更干净。由于不知道如何编写更通用的helper函数,我提出了以下建议: let (!?) x = if Double.IsNaN(x) then None else Some (decimal x) let (!?) (x, y) = match (x, y) with | (x, y)

我正在写一些代码来处理一个大数据文件。如果字段为none,CSV TypeProvider将返回给定值的NaN。我有很多字段,所以如果我编写一个helper函数来检查NaN并返回一个None,会更干净。由于不知道如何编写更通用的helper函数,我提出了以下建议:

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
请注意,我必须创建一种将
^b
T
统一的方法,这就是我添加
调用
函数的原因

现在这仍然有效,它是一个有效的解决方案,可以清除重载签名并在解析函数中添加更多的噪声,这在许多情况下是一个很好的折衷方案。但是如果我从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
~vs6086.fsx(11,27):错误FS0043:无法根据此程序点之前的类型信息确定方法“op_-Dollar”的唯一重载。可能需要类型注释。
已知返回类型:“b”
已知类型参数:<^a>
候选人:
-静态成员T.($):(浮点*浮点)->(十进制*十进制)选项
-静态成员T.($):x:float->decimal选项
为什么呢

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

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

通过了解所有类型参数,它尝试应用标准的.Net重载解析,但失败了,因为有两个候选类型

希望这个解释有意义。

F#绑定只有一个定义。再次定义它只会使以前的定义变得模糊

let f x = x
let f x = x * x //previous f is now shadowed
这里有一种使用静态解析类型来实现相同结果的方法。 我们正在定义名为
Converter
的中间类型(单例DU)上的成员:

type Converter = Converter with
    static member ToOption (_ : Converter, value) = 
        if Double.IsNaN(value) then None else Some(value)

    static member ToOption (_ : Converter, (x, y)) = 
        if Double.IsNaN(x) || Double.IsNaN(y) then None else Some(x, y)

let inline (!?) (x : ^a) =
    ((^b or ^a) : (static member ToOption : ^b * ^a -> _) (Converter, x))

这是一个非常有用的答案,但是你能解释一下为什么延迟重载解析是有益的吗?我添加了一个编辑,并用示例代码对其进行了详细的解释。@LitiumFrost我认为这应该作为答案加以说明。里面有很多好信息。@Asti推荐被接受!我还在想办法解决这个问题,但是额外的信息和示例非常有用。我想也许F#会支持不同签名的函数重载,比如C#支持方法重载。知道它是阴影,这很有帮助。不过,我不清楚内联函数定义发生了什么。函数签名指定^a类型的单个参数,但签名体使用^b。这是从哪里来的?F#对成员函数具有重载(与C#相同,但不适用于绑定)^b是“hat类型”-F#不解析静态解析类型绑定中的扩展方法。因此,我们在这里使用了一个额外的类型
Converter
——您会注意到
ToOption
函数签名实际上是
ToOption(Converter,argument)
——我们使用它来定位函数实际定义的类型。这有点迂回。