f# - 如何编写通用数字的函数?

标签 f# types numbers generics type-inference

我对 F# 还很陌生,发现类型推断确实是一件很酷的事情。但目前看来,这也可能导致代码重复,这不是一件很酷的事情。我想对一个数字的数字进行求和,如下所示:

let rec crossfoot n =
  if n = 0 then 0
  else n % 10 + crossfoot (n / 10)

crossfoot 123

这会正确打印 6。但现在我输入的数字不适合 int 32 位,所以我必须将其转换为。

let rec crossfoot n =
  if n = 0L then 0L
  else n % 10L + crossfoot (n / 10L)

crossfoot 123L

然后,一个 BigInteger 出现了,你猜怎么着……

当然,我只能使用 bigint 版本,并根据需要向上转换输入参数并向下转换输出参数。但首先我假设使用 BigInteger 而不是 int 会带来一些性能损失。第二个 let cf = int (crossfoot (bigint 123)) 读起来不太好。

没有通用的方法来编写这个吗?

最佳答案

基于 Brian 和 Stephen 的答案,这里有一些完整的代码:

module NumericLiteralG = 
    let inline FromZero() = LanguagePrimitives.GenericZero
    let inline FromOne() = LanguagePrimitives.GenericOne
    let inline FromInt32 (n:int) =
        let one : ^a = FromOne()
        let zero : ^a = FromZero()
        let n_incr = if n > 0 then 1 else -1
        let g_incr = if n > 0 then one else (zero - one)
        let rec loop i g = 
            if i = n then g
            else loop (i + n_incr) (g + g_incr)
        loop 0 zero 

let inline crossfoot (n:^a) : ^a =
    let (zero:^a) = 0G
    let (ten:^a) = 10G
    let rec compute (n:^a) =
        if n = zero then zero
        else ((n % ten):^a) + compute (n / ten)
    compute n

crossfoot 123
crossfoot 123I
crossfoot 123L


更新:简单答案

这是一个独立的实现,没有 NumericLiteralG 模块,并且推断类型限制稍少:

let inline crossfoot (n:^a) : ^a =
    let zero:^a = LanguagePrimitives.GenericZero
    let ten:^a = (Seq.init 10 (fun _ -> LanguagePrimitives.GenericOne)) |> Seq.sum
    let rec compute (n:^a) =
        if n = zero then zero
        else ((n % ten):^a) + compute (n / ten)
    compute n

说明

F# 中实际上有两种类型的泛型:1) 通过 .NET 接口(interface)/继承的运行类型多态性,以及 2) 编译时泛型。需要编译时泛型来适应泛型数值运算和鸭子类型( explicit member constraints )之类的内容。这些功能是 F# 不可或缺的一部分,但在 .NET 中不受支持,因此必须在编译时由 F# 处理。

插入符号 (^) 用于区分 statically resolved (compile-time) type parameters来自普通的(使用撇号)。简而言之,'a 在运行时处理,^a 在编译时处理,这就是函数必须标记为内联的原因。

我以前从未尝试过写这样的东西。结果比我预想的要笨拙。我认为在 F# 中编写通用数字代码的最大障碍是:创建除零或一之外的通用数字的实例。请参阅 this answerFromInt32 的实现看看我的意思。 GenericZeroGenericOne 是内置的,它们是使用用户代码中不可用的技术实现的。在此函数中,由于我们只需要一个小数字 (10),因此我创建了一个由 10 个 GenericOne 组成的序列并对它们求和。

我无法解释为什么需要所有类型注释,只能说每次编译器遇到泛型类型的操作时它都会出现,它似乎认为它正在处理新类型。因此它最终会推断出一些具有重复限制的奇怪类型(例如,它可能需要多次 (+) )。添加类型注释可以让它知道我们自始至终都在处理相同的类型。没有它们,代码可以正常工作,但添加它们可以简化推断的签名。

关于f# - 如何编写通用数字的函数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4732672/

相关文章:

f# - List.fold和List.foldBack之间的区别的示例

mysql - 如何在 MySQL 中重命名表并更改存储类型?

python - 不会添加计数mastermind游戏python

function - 关于偶数和奇数的 Haskell 函数

F# 数据 : JSON Parser. 使用 JsonExtensions

arrays - F# 数组中的负索引

f# - 类型级别数算术

types - 在插件架构中扩展类型

.net - 如何在 CIL 中的堆栈上处理不同的类型

C 计算一个数字出现的次数