如何编写泛型数字的函数? [英] How to write a function for generic numbers?
问题描述
我对F#很陌生,发现类型推断确实是一件很酷的事情。但目前看起来它也可能导致代码重复,这并不是一件很酷的事情。我想总结这样一个数字的数字:
let rec crossfoot n =
如果n = 0那么0
else n%10 + crossfoot(n / 10)
crossfoot 123
正确打印 6
。但现在我的输入数字不适合int 32位,所以我必须将其转换为。
let rec crossfoot n =
$ p $ >
if n = 0L然后0L
else n%10L + crossfoot(n / 10L)
crossfoot 123L
然后,一个
BigInteger
来我的方式,并猜测...
<当然,我只能使用bigint
版本,并根据需要向上投射输入参数和输出参数。但首先我假设在int
上使用BigInteger
会有一些性能损失。第二个让cf = int(crossfoot(bigint 123))
不会读得好。
解决方案基于Brian's和Stephen的答案,以下是一些完整的代码:
module NumericLiteralG =
let inline FromZero()= LanguagePrimitives.GenericZero
let inline FromOne()= LanguagePrimitives.GenericOne
让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 ig =
if i = n then g
else loop(i + n_incr)(g + g_incr)
loop 0零
让inline crossfoot(n:^ a):^ a =
let(zero:^ a)= 0G
let(ten:^ a)= 10G
让rec计算(n:^ a)=
,如果n = 0,则零
else((n%ten):^ a)+ compute(n / 10)
计算n
crossfoot 123
crossfoot 123I
crossfoot 123L
UPDATE:Simple Answer
这是一个独立的实现,没有
NumericLiteralG
模块,并且推断类型的限制略少:
let inline crossfoot(n:^ a):^ a =
let zero:^ a = LanguagePrimitives.GenericZero
let ten:^ a =(Seq.init 10(fun_ - > LanguagePrimitives.GenericOne))|> Seq.sum
让rec计算(n:^ a)=
如果n = 0,则为零
else((n%ten):^ a)+ compute(n / ten)
计算n
解释
F#中有两种类型的泛型:1)运行类型多态,通过.NET接口/继承,以及2)编译时泛型。编译时泛型需要适应诸如泛型数值操作之类的东西,以及类似鸭子的输入(显式成员约束)。这些特性是F#不可或缺的一部分,但在.NET中不受支持,因此必须在编译时由F#处理。
$ b插入符号(
^
)用于区分静态解析(编译时)键入参数从普通的(使用撇号)。简而言之,在运行时处理'a
,编译时处理^ a
- 这就是为什么函数必须标记为inline
。
我以前从来没有试过写过类似的东西。结果比我预期的更笨拙。我认为在F#中编写通用数字代码的最大障碍是:创建非零或一个通用数字的实例。请参阅 FromInt32 >这个答案,看看我的意思。
GenericZero
和GenericOne
是内置的,它们使用用户代码中不可用的技术实现。在这个函数中,由于我们只需要一个小数(10),我创建了一个10GenericOne
s的序列并且将它们相加。
我无法解释为什么需要所有的类型注释,除非说每次编译器遇到对泛型类型的操作时,它似乎认为它正在处理新类型。因此,它最终会推断出一些具有重复重复的奇怪类型(例如,它可能需要多次
(+)
)。添加类型注释让它知道我们在处理相同的类型。没有它们,代码就能正常工作,但添加它们会简化推断的签名。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
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
Then, a
BigInteger
comes my way and guess what…Of course, I could only have the
bigint
version and cast input parameters up and output parameters down as needed. But first I assume usingBigInteger
overint
has some performance penalities. Secondlet cf = int (crossfoot (bigint 123))
does just not read nice.Isn't there a generic way to write this?
解决方案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
UPDATE: Simple Answer
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
Explanation
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.
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 markedinline
.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
andGenericOne
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 10GenericOne
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屋!