我正在编写一些代码来处理大型数据文件。如果该字段为 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
请注意,我必须创建一种将 ^b
与 T
统一的方法,这就是我添加 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
未知,^b
与 T
统一,但由于另一个未知,因此重载解析被延迟到 parse
函数的每个 future 单独调用,这是可行的,因为它被声明为内联。
通过了解所有类型参数,它尝试应用标准的 .Net 重载解决方案,但由于有 2 个候选者而失败。
希望这个解释有意义。
关于generics - F# 中有没有办法编写一个可以接受 Tuple 或 Single 值的通用函数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60963773/