如何为通用数字编写函数? [英] How to write a function for generic numbers?
问题描述
我对 F# 很陌生,我发现类型推断确实是一件很酷的事情.但目前看来,它也可能导致代码重复,这不是一件很酷的事情.我想像这样对数字的数字求和:
I'm quite new to F# and find type inference really is a cool thing. But currently it seems that it also may lead to code duplication, which is not a cool thing. I want to sum the digits of a number like this:
let rec crossfoot n =
if n = 0 then 0
else n % 10 + crossfoot (n / 10)
crossfoot 123
这正确打印了 6
.但是现在我输入的数字不适合int 32位,所以我必须把它转换成.
This correctly prints 6
. But now my input number does not fit int 32 bits, so I have to transform it to.
let rec crossfoot n =
if n = 0L then 0L
else n % 10L + crossfoot (n / 10L)
crossfoot 123L
然后,一个 BigInteger
出现在我面前,猜猜是什么......
Then, a BigInteger
comes my way and guess what…
当然,我只能使用 bigint
版本并根据需要向上转换输入参数和向下转换输出参数.但首先我假设在 int
上使用 BigInteger
有一些性能损失.第二个 let cf = int (crossfoot (bigint 123))
读起来不太好.
Of course, I could only have the bigint
version and cast input parameters up and output parameters down as needed. But first I assume using BigInteger
over int
has some performance penalities. Second let cf = int (crossfoot (bigint 123))
does just not read nice.
没有通用的写法吗?
推荐答案
基于 Brian 和 Stephen 的回答,这里有一些完整的代码:
Building on Brian's and Stephen's answers, here's some complete code:
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
模块,并且推断类型的限制稍少:
Here's a standalone implementation, without the NumericLiteralG
module, and a slightly less restrictive inferred type:
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 接口/继承,2) 编译时泛型.需要编译时泛型来适应诸如泛型数值运算和鸭子类型(显式成员约束).这些功能是 F# 不可或缺的,但在 .NET 中不受支持,因此必须在编译时由 F# 处理.
There are effectively two types of generics in F#: 1) run-type polymorphism, via .NET interfaces/inheritance, and 2) compile time generics. Compile-time generics are needed to accommodate things like generic numerical operations and something like duck-typing (explicit member constraints). These features are integral to F# but unsupported in .NET, so therefore have to be handled by F# at compile time.
插入符号 (^
) 用于区分 从普通参数(使用撇号)静态解析(编译时)类型参数.简而言之,'a
在运行时处理,^a
在编译时处理——这就是函数必须标记为inline
的原因.
The caret (^
) is used to differentiate statically resolved (compile-time) type parameters from ordinary ones (which use an apostrophe). In short, 'a
is handled at run-time, ^a
at compile-time–which is why the function must be marked inline
.
我以前从未尝试过写这样的东西.结果比我预期的要笨拙.我看到在 F# 中编写通用数字代码的最大障碍是:创建非零或一的通用数字的实例.在这个答案FromInt32的实现> 明白我的意思.GenericZero
和 GenericOne
是内置的,它们是使用用户代码中不可用的技术实现的.在这个函数中,由于我们只需要一个很小的数字(10),我创建了一个由 10 个 GenericOne
组成的序列并将它们相加.
I had never tried to write something like this before. It turned out clumsier than I expected. The biggest hurdle I see to writing generic numeric code in F# is: creating an instance of a generic number other than zero or one. See the implementation of FromInt32
in this answer to see what I mean. GenericZero
and GenericOne
are built-in, and they're implemented using techniques that aren't available in user code. In this function, since we only needed a small number (10), I created a sequence of 10 GenericOne
s and summed them.
我也无法解释为什么需要所有类型注释,只是说每次编译器遇到对泛型类型的操作时都会出现它似乎认为它正在处理新类型.因此,它最终会推断出一些具有重复限制的奇怪类型(例如,它可能需要多次 (+)
).添加类型注释让它知道我们一直在处理相同的类型.没有它们,代码也能正常工作,但添加它们会简化推断的签名.
I can't explain as well why all the type annotations are needed, except to say that it appears each time the compiler encounters an operation on a generic type it seems to think it's dealing with a new type. So it ends up inferring some bizarre type with duplicated resitrictions (e.g. it may require (+)
multiple times). Adding the type annotations lets it know we're dealing with the same type throughout. The code works fine without them, but adding them simplifies the inferred signature.
这篇关于如何为通用数字编写函数?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!