如何编写通用数字的函数?

我对F#很陌生,发现types推断确实是一件很酷的事情。 但目前看来,这也可能导致代码重复,这不是一件很酷的事情 。 我想总结一个这样的数字的数字:

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

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

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

然后,一个BigInteger来我的方式,并猜测是什么…

当然,我只能拥有bigint版本,并根据需要向上投入input参数和输出参数。 但是首先我假设在int上使用BigInteger有一些性能惩罚。 其次, 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 ig = 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#中有两种types的generics:1)运行型多态,通过.NET接口/inheritance,2)编译时generics。 编译时generics需要适应像通用数值操作之类的东西,以及像鸭子键入( 显式成员约束 )的东西。 这些特性是F#所不可或缺的一部分,但在.NET中不受支持,所以在编译时必须由F#来处理。

脱字符( ^ )用于区分静态parsing(编译时)types参数和普通types (使用撇号)。 简而言之, 'a在运行时处理, ^a在编译时 – 这就是为什么函数必须标记为inline

我从来没有试过写过这样的东西。 结果比我预期的更笨拙。 我认为在F#中编写通用数字代码的最大障碍是:创build非零或一个通用数字的实例。 在这个答案中看到FromInt32的实现来看看我的意思。 GenericZeroGenericOne是内置的,它们使用用户代码中不可用的技术来实现。 在这个函数中,由于我们只需要一个很小的数(10),所以我创build了一个10个GenericOne的序列并对它们进行求和。

我不能解释为什么需要所有的types注释,除非说每次编译器遇到对genericstypes的操作,似乎认为它是处理新types。 所以它最终推断出一些奇怪的types与重复resitrictions(例如它可能需要(+)多次)。 添加types注释让它知道我们在处理相同的types。 没有他们的代码工作正常,但添加他们简化了推断的签名。

除了使用数字文字(Brian's link)的kvb技术之外,我还使用了一种可以产生更好的推断结构types签名的不同技术,并且还可以用来创build预先计算的types特定的函数以获得更好的性能作为对受支持的数字types的控制(因为您经常需要支持所有整型,但不是理性types): F#静态成员types约束 。

继丹尼尔和我对由不同技术产生的推断types签名的讨论之后,下面是一个概述:

NumericLiteralG技术

 module NumericLiteralG = let inline FromZero() = LanguagePrimitives.GenericZero let inline FromOne() = LanguagePrimitives.GenericOne let inline FromInt32 (n:int) = let one = FromOne() let zero = 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 zero 

Crossfoot不添加任何types的注释:

 let inline crossfoot1 n = let rec compute n = if n = 0G then 0G else n % 10G + compute (n / 10G) compute n val inline crossfoot1 : ^a -> ^e when ( ^a or ^b) : (static member ( % ) : ^a * ^b -> ^d) and ^a : (static member get_Zero : -> ^a) and ( ^a or ^f) : (static member ( / ) : ^a * ^f -> ^a) and ^a : equality and ^b : (static member get_Zero : -> ^b) and ( ^b or ^c) : (static member ( - ) : ^b * ^c -> ^c) and ( ^b or ^c) : (static member ( + ) : ^b * ^c -> ^b) and ^c : (static member get_One : -> ^c) and ( ^d or ^e) : (static member ( + ) : ^d * ^e -> ^e) and ^e : (static member get_Zero : -> ^e) and ^f : (static member get_Zero : -> ^f) and ( ^f or ^g) : (static member ( - ) : ^f * ^g -> ^g) and ( ^f or ^g) : (static member ( + ) : ^f * ^g -> ^f) and ^g : (static member get_One : -> ^g) 

Crossfoot添加一些types的注释:

 let inline crossfoot2 (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 val inline crossfoot2 : ^a -> ^a when ^a : (static member get_Zero : -> ^a) and ( ^a or ^a0) : (static member ( - ) : ^a * ^a0 -> ^a0) and ( ^a or ^a0) : (static member ( + ) : ^a * ^a0 -> ^a) and ^a : equality and ^a : (static member ( + ) : ^a * ^a -> ^a) and ^a : (static member ( % ) : ^a * ^a -> ^a) and ^a : (static member ( / ) : ^a * ^a -> ^a) and ^a0 : (static member get_One : -> ^a0) 

loggingtypes技术

 module LP = let inline zero_of (target:'a) : 'a = LanguagePrimitives.GenericZero<'a> let inline one_of (target:'a) : 'a = LanguagePrimitives.GenericOne<'a> let inline two_of (target:'a) : 'a = one_of(target) + one_of(target) let inline three_of (target:'a) : 'a = two_of(target) + one_of(target) let inline negone_of (target:'a) : 'a = zero_of(target) - one_of(target) let inline any_of (target:'a) (x:int) : 'a = let one:'a = one_of target let zero:'a = zero_of target let xu = if x > 0 then 1 else -1 let gu:'a = if x > 0 then one else zero-one let rec get ig = if i = x then g else get (i+xu) (g+gu) get 0 zero type G<'a> = { negone:'a zero:'a one:'a two:'a three:'a any: int -> 'a } let inline G_of (target:'a) : (G<'a>) = { zero = zero_of target one = one_of target two = two_of target three = three_of target negone = negone_of target any = any_of target } open LP 

十字脚,不需要注释就可以得到很好的推断签名:

 let inline crossfoot3 n = let g = G_of n let ten = g.any 10 let rec compute n = if n = g.zero then g.zero else n % ten + compute (n / ten) compute n val inline crossfoot3 : ^a -> ^a when ^a : (static member ( % ) : ^a * ^a -> ^b) and ( ^b or ^a) : (static member ( + ) : ^b * ^a -> ^a) and ^a : (static member get_Zero : -> ^a) and ^a : (static member get_One : -> ^a) and ^a : (static member ( + ) : ^a * ^a -> ^a) and ^a : (static member ( - ) : ^a * ^a -> ^a) and ^a : equality and ^a : (static member ( / ) : ^a * ^a -> ^a) 

Crossfoot,没有注释,接受预先计算的G:

 let inline crossfootG g ten n = let rec compute n = if n = g.zero then g.zero else n % ten + compute (n / ten) compute n val inline crossfootG : G< ^a> -> ^b -> ^a -> ^a when ( ^a or ^b) : (static member ( % ) : ^a * ^b -> ^c) and ( ^c or ^a) : (static member ( + ) : ^c * ^a -> ^a) and ( ^a or ^b) : (static member ( / ) : ^a * ^b -> ^a) and ^a : equality 

我在实践中使用了上面的内容,从那时起,我可以使预先计算types的特定版本不受Generic LanguagePrimitives性能代价的影响:

 let gn = G_of 1 //int32 let gL = G_of 1L //int64 let gI = G_of 1I //bigint let gD = G_of 1.0 //double let gS = G_of 1.0f //single let gM = G_of 1.0m //decimal let crossfootn = crossfootG gn (gn.any 10) let crossfootL = crossfootG gL (gL.any 10) let crossfootI = crossfootG gI (gI.any 10) let crossfootD = crossfootG gD (gD.any 10) let crossfootS = crossfootG gS (gS.any 10) let crossfootM = crossfootG gM (gM.any 10) 

由于在使用广义数字文字时如何使types签名变得毛茸茸的问题已经出现,我想我会把我的两分钱。 主要问题是F#的操作符可以是非对称的,这样你就可以执行诸如System.DateTime.Now + System.TimeSpan.FromHours(1.0)这样的东西,这意味着F#的types推断在执行算术操作时添加中间typesvariables。

在数值algorithm的情况下,这种潜在的不对称通常不是有用的,并且types签名的爆炸是相当丑陋的(尽pipe当给定具体参数时它通常不会影响F#正确应用函数的能力)。 这个问题的一个可能的解决scheme是在你关心的范围内限制算术运算符的types。 例如,如果你定义这个模块:

 module SymmetricOps = let inline (+) (x:'a) (y:'a) : 'a = x + y let inline (-) (x:'a) (y:'a) : 'a = x - y let inline (*) (x:'a) (y:'a) : 'a = x * y let inline (/) (x:'a) (y:'a) : 'a = x / y let inline (%) (x:'a) (y:'a) : 'a = x % y ... 

那么只要希望运算符只应用于两个相同types的参数,就可以打开SymmetricOps模块。 所以现在我们可以定义:

 module NumericLiteralG = open SymmetricOps let inline FromZero() = LanguagePrimitives.GenericZero let inline FromOne() = LanguagePrimitives.GenericOne let inline FromInt32 (n:int) = let one = FromOne() let zero = 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 zero 

 open SymmetricOps let inline crossfoot x = let rec compute n = if n = 0G then 0G else n % 10G + compute (n / 10G) compute x 

而推断的types是比较干净的

 val inline crossfoot : ^a -> ^a when ^a : (static member ( - ) : ^a * ^a -> ^a) and ^a : (static member get_One : -> ^a) and ^a : (static member ( % ) : ^a * ^a -> ^a) and ^a : (static member get_Zero : -> ^a) and ^a : (static member ( + ) : ^a * ^a -> ^a) and ^a : (static member ( / ) : ^a * ^a -> ^a) and ^a : equality 

而我们仍然得到一个好的,可读的crossfoot定义的crossfoot

看到

F#的隐藏特性

当我正在寻找一个解决scheme时,我偶然发现了这个话题,并发布了我的答案,因为我find了一种expressiongenerics数字的方法,而不需要手动构build数字的不太理想的实现。

 open System.Numerics // optional open MathNet.Numerics module NumericLiteralG = type GenericNumber = GenericNumber with static member instance (GenericNumber, x:int32, _:int8) = fun () -> int8 x static member instance (GenericNumber, x:int32, _:uint8) = fun () -> uint8 x static member instance (GenericNumber, x:int32, _:int16) = fun () -> int16 x static member instance (GenericNumber, x:int32, _:uint16) = fun () -> uint16 x static member instance (GenericNumber, x:int32, _:int32) = fun () -> x static member instance (GenericNumber, x:int32, _:uint32) = fun () -> uint32 x static member instance (GenericNumber, x:int32, _:int64) = fun () -> int64 x static member instance (GenericNumber, x:int32, _:uint64) = fun () -> uint64 x static member instance (GenericNumber, x:int32, _:float32) = fun () -> float32 x static member instance (GenericNumber, x:int32, _:float) = fun () -> float x static member instance (GenericNumber, x:int32, _:bigint) = fun () -> bigint x static member instance (GenericNumber, x:int32, _:decimal) = fun () -> decimal x static member instance (GenericNumber, x:int32, _:Complex) = fun () -> Complex.op_Implicit x static member instance (GenericNumber, x:int64, _:int64) = fun () -> int64 x static member instance (GenericNumber, x:int64, _:uint64) = fun () -> uint64 x static member instance (GenericNumber, x:int64, _:float32) = fun () -> float32 x static member instance (GenericNumber, x:int64, _:float) = fun () -> float x static member instance (GenericNumber, x:int64, _:bigint) = fun () -> bigint x static member instance (GenericNumber, x:int64, _:decimal) = fun () -> decimal x static member instance (GenericNumber, x:int64, _:Complex) = fun () -> Complex.op_Implicit x static member instance (GenericNumber, x:string, _:float32) = fun () -> float32 x static member instance (GenericNumber, x:string, _:float) = fun () -> float x static member instance (GenericNumber, x:string, _:bigint) = fun () -> bigint.Parse x static member instance (GenericNumber, x:string, _:decimal) = fun () -> decimal x static member instance (GenericNumber, x:string, _:Complex) = fun () -> Complex(float x, 0.0) // MathNet.Numerics static member instance (GenericNumber, x:int32, _:Complex32) = fun () -> Complex32.op_Implicit x static member instance (GenericNumber, x:int32, _:bignum) = fun () -> bignum.FromInt x static member instance (GenericNumber, x:int64, _:Complex32) = fun () -> Complex32.op_Implicit x static member instance (GenericNumber, x:int64, _:bignum) = fun () -> bignum.FromBigInt (bigint x) static member instance (GenericNumber, x:string, _:Complex32) = fun () -> Complex32(float32 x, 0.0f) static member instance (GenericNumber, x:string, _:bignum) = fun () -> bignum.FromBigInt (bigint.Parse x) let inline genericNumber num = Inline.instance (GenericNumber, num) () let inline FromZero () = LanguagePrimitives.GenericZero let inline FromOne () = LanguagePrimitives.GenericOne let inline FromInt32 n = genericNumber n let inline FromInt64 n = genericNumber n let inline FromString n = genericNumber n 

这个实现是通过在投射期间没有复杂的迭代来实现的。 它使用FsControl作为实例模块。

http://www.fssnip.net/mv

十字脚正是你想要做的,还是只是总结一个长号的数字?

因为如果你只是想总结数字,那么:

 let crossfoot (x:'a) = x.ToString().ToCharArray() |> (Array.fold(fun acc x' -> if x' <> '.' then acc + (int x') else acc) 0) 

…你完成了。

无论如何,你可以将东西转换成string,放下小数点,记住小数点的位置,把它解释为一个int,运行crossfoot?

这是我的解决scheme。 我不确定你想要什么“crossfoot”工作,当你有一个小数点添加。

例如,你想要: crossfoot(123.1) = 7crossfoot(123.1) = 6.1 ? (我假设你想要后者)

无论如何,代码确实允许你使用数字作为generics。

 let crossfoot (n:'a) = // Completely generic input let rec crossfoot' (a:int) = // Standard integer crossfoot if a = 0 then 0 else a%10 + crossfoot' (a / 10) let nstr = n.ToString() let nn = nstr.Split([|'.'|]) // Assuming your main constraint is float/int let n',n_ = if nn.Length > 1 then nn.[0],nn.[1] else nn.[0],"0" let n'',n_' = crossfoot'(int n'),crossfoot'(int n_) match n_' with | 0 -> string n'' | _ -> (string n'')+"."+(string n_') 

如果你需要input大整数或int64的东西,crossfoot的工作方式,你可以把大数字分割成块(string),并把它们送入这个函数,并将它们加在一起。