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 using BigInteger
over int
has some performance penalities. Second let cf = int (crossfoot (bigint 123))
does just not read nice.
Isn't there a generic way to write this?
See
Hidden Features of F#
Building on Brian's and Stephen's answers, here's some complete code:
UPDATE: Simple Answer
Here's a standalone implementation, without the
NumericLiteralG
module, and a slightly less restrictive inferred type: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.Since the question of how to make the type signatures less hairy when using the generalized numeric literals has come up, I thought I'd put in my two cents. The main issue is that F#'s operators can be asymmetric so that you can do stuff like
System.DateTime.Now + System.TimeSpan.FromHours(1.0)
, which means that F#'s type inference adds intermediary type variables whenever arithmetic operations are being performed.In the case of numerical algorithms, this potential asymmetry isn't typically useful and the resulting explosion in the type signatures is quite ugly (although it generally doesn't affect F#'s ability to apply the functions correctly when given concrete arguments). One potential solution to this problem is to restrict the types of the arithmetic operators within the scope that you care about. For instance, if you define this module:
then you can just open the
SymmetricOps
module whenever you want have the operators apply only to two arguments of the same type. So now we can define:and
and the inferred type is the relatively clean
while we still get the benefit of a nice, readable definition for
crossfoot
.I stumbled upon this topic when I was looking for a solution and I am posting my answer, because I found a way to express generic numeral without the less than optimal implementation of building up the number by hand.
this implementation comes by without complicated iteration during the cast. It uses FsControl for the Instance module.
http://www.fssnip.net/mv
In addition to kvb's technique using Numeric Literals (Brian's link), I've had a lot of success using a different technique which can yield better inferred structural type signatures and may also be used to create precomputed type-specific functions for better performance as well as control over supported numeric types (since you will often want to support all integral types, but not rational types, for example): F# Static Member Type Constraints.
Following up on the discussion Daniel and I have been having about the inferred type signatures yielded by the different techniques, here is an overview:
NumericLiteralG Technique
Crossfoot without adding any type annotations:
Crossfoot adding some type annotations:
Record Type Technique
Crossfoot, no annotations required for nice inferred signature:
Crossfoot, no annotations, accepts precomputed instances of G:
I use the above in practice since then I can make precomputed type specific versions which don't suffer from the performance cost of Generic LanguagePrimitives:
Is crossfoot exactly what you want to do, or is it just summing the digits of a long number?
because if you just want to sum the digits, then:
... And you are done.
Anyways, Can you convert stuff to a string, drop the decimal point, remember where the decimal point is, interpret it as an int, run crossfoot?
Here is my solution. I am not sure exactly how you want "crossfoot" to work when you have a decimal point added.
For instance, do you want:
crossfoot(123.1) = 7
orcrossfoot(123.1) = 6.1
? (I'm assuming you want the latter)Anyways, the code does allow you to work with numbers as generics.
If you need to input big integers or int64 stuff, the way crossfoot works, you can just split the big number into bitesize chunks (strings) and feed them into this function, and add them together.