Как написать функцию для общих чисел?

Я совершенно новичок в F # и считаю, что вывод типа действительно classный. Но в настоящее время кажется, что это также может привести к дублированию кода, что не очень круто . Я хочу суммировать цифры номера следующим образом:

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 версию bigint и вводить параметры ввода и выводить параметры по мере необходимости. Но сначала я предполагаю, что использование BigInteger над int имеет некоторые нарушения производительности. Второй let cf = int (crossfoot (bigint 123)) просто не читается хорошо.

Разве нет общего способа написать это?

Основываясь на ответах Брайана и Стивена, вот несколько полных кодов:

 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 

UPDATE: простой ответ

Вот автономная реализация, без модуля 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) polymorphism типа запуска через интерфейсы / наследование .NET и 2) обобщенные обобщенные данные. Компиляционные генераторы необходимы для размещения таких вещей, как общие числовые операции и что-то вроде утиного ввода ( явные ограничения для членов ). Эти функции являются неотъемлемой частью F #, но не поддерживаются в .NET, поэтому их нужно обрабатывать F # во время компиляции.

Каретка ( ^ ) используется для дифференциации параметров статического разрешения (времени компиляции) из обычных (которые используют апостроф). Короче говоря, 'a обрабатывается во время выполнения, ^a во время компиляции, поэтому функция должна быть отмечена как inline .

Я никогда не пытался писать что-то подобное раньше. Это оказалось неуклюже, чем я ожидал. Самое большое препятствие, которое я вижу для написания общего числового кода в F #, – это создание экземпляра общего номера, отличного от нуля или одного. См. Реализацию FromInt32 в этом ответе, чтобы понять, что я имею в виду. GenericZero и GenericOne встроены, и они реализованы с использованием техник, которые недоступны в коде пользователя. В этой функции, поскольку нам понадобилось лишь небольшое число (10), я создал последовательность из 10 GenericOne s и суммировал их.

Я также не могу объяснить, почему нужны все annotations типа, за исключением того, что каждый раз, когда компилятор сталкивается с операцией на родовом типе, кажется, что он имеет дело с новым типом. Таким образом, он заканчивает вывод какого-то странного типа с дублирующимися переписями (например, он может потребовать (+) несколько раз). Добавление аннотаций типа позволяет узнать, что мы имеем дело с одним и тем же типом. Код отлично работает без них, но добавление их упрощает выводимую подпись.

В дополнение к методу kvb, использующему числовые литералы (ссылка Брайана), я имел большой успех, используя другую технику, которая может давать более понятные структурные сигнатуры типа, а также может использоваться для создания предварительно вычисляемых функций типа для лучшей производительности как контроль над поддерживаемыми числовыми типами (поскольку вы часто захотите, например, поддерживать все интегральные типы, но не рациональные типы): F # Статические ограничения типа пользователя .

Следуя обсуждению, Дэниэл и я были о предполагаемых типах сигнатур, полученных различными методами, вот краткий обзор:

Метод 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 без добавления аннотаций типа:

 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 добавляет annotations некоторых типов:

 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) 

Тип записи

 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 

Crossfoot, никаких аннотаций, необходимых для красивой предполагаемой подписи:

 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, no annotations, принимает предварительно вычисленные экземпляры 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 

Я использую вышеприведенное на практике с тех пор, как я могу сделать предварительно вычислительные версии, специфичные для типа, которые не страдают от стоимости производительности общих понятий языка:

 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) 

Поскольку вопрос о том, как сделать сигнатуры типов менее волосатыми при использовании обобщенных числовых литералов, я подумал, что поставлю свои два цента. Основная проблема заключается в том, что операторы F # могут быть асимметричными, так что вы можете делать такие вещи, как System.DateTime.Now + System.TimeSpan.FromHours(1.0) , что означает, что вывод типа F # добавляет переменные промежуточного типа всякий раз, когда выполняются арифметические операции.

В случае числовых алгоритмов эта потенциальная асимметрия обычно не бывает полезной, и возникающий в результате взрыва в сигнатурах типа довольно уродлив (хотя, как правило, это не влияет на способность F # правильно применять функции при заданных конкретных аргументах). Одним из возможных решений этой проблемы является ограничение типов арифметических операторов в пределах области действия, о которой вы заботитесь. Например, если вы определяете этот модуль:

 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 ... 

то вы можете просто открыть модуль 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 

и предполагаемый тип является относительно чистым

 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 .

Видеть

Скрытые особенности F #

Я наткнулся на эту тему, когда искал решение, и я отправляю свой ответ, потому что нашел способ выразить цифровую цифру без оптимальной реализации наращивания количества вручную.

 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 для модуля Instance.

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) 

… И все готово.

В любом случае, можете ли вы преобразовать материал в строку, отбросить десятичную точку, вспомнить, где находится десятичная точка, интерпретировать ее как int, запустить crossfoot?

Вот мое решение. Я не уверен точно, как вы хотите, чтобы «кроссфут» работал, когда вы добавили десятичную точку.

Например, вы хотите: crossfoot(123.1) = 7 или crossfoot(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_') 

Если вам нужно вводить большие целые числа или объекты int64, то, как работает Crossfoot, вы можете просто разделить большое количество на куски bitesize (строки) и передать их в эту функцию и добавить их вместе.

Давайте будем гением компьютера.