It's easy to write a function that adds two int
s in F#:
let add x y = x + y
Actually, it's the same as:
let add (x:int) (y:int): int = x + y
If you need to make it generic so that it can take arguments of other type than int
,
you should use inline
keyword:
> let inline add x y = x + y;;
val inline add :
x: ^a -> y: ^b -> ^c
when ( ^a or ^b) : (static member ( + ) : ^a * ^b -> ^c)
Then you can pass values of any type that has the + operator to add
:
> add 1 2;;
val it : int = 3
> add 1L 2L;;
val it : int64 = 3L
> add 1I 2I;;
val it : System.Numerics.BigInteger = 3
However, it's not easy to write a generic function if you have to use literals in the middle of an expression:
> let inline inc x = x + 1;;
val inline inc : x:int -> int
x
always evaluates to be of int
since the literal 1
is of int
. It would be cool if you could write something like the following pseudo-code:
let inline inc (x:^T) = x + (1 :> ^T)
In order to do so, F# provides NumericLiteralX
(where X
should be replaced with G
, N
, Z
, ...):
module NumericLiteralG =
let inline FromZero() = LanguagePrimitives.GenericZero
let inline FromOne() = LanguagePrimitives.GenericOne
let inline FromInt32 n =
let one = FromOne()
let zero = FromZero()
let iinc = if n > 0 then 1 else -1
let ginc = if n > 0 then one else -one
let rec loop i g =
if i = n then g
else loop (i + iinc) (g + ginc)
loop 0 zero
Now you can make the inc
function generic as follows:
> let inline inc x = x + 1G;;
val inline inc :
x: ^a -> ^c
when ( ^a or ^b) : (static member ( + ) : ^a * ^b -> ^c) and
^b : (static member get_One : -> ^b)
The obvious deficiency with NumericLiteralX
, however, is
FromInt32
can be horribly slow for a large number. For example, if you write
a function that divides a number by 1000000:
let inline divideByMillion x = x / 1000000G
the loop in FromInt32
executes 1000000 times!
Here I propose a better approach that has no such performance hit:
type BigIntCast = BigIntCast with
static member inline (=>) (BigIntCast, x: int) = bigint x
static member inline (=>) (BigIntCast, x: uint32) = bigint x
static member inline (=>) (BigIntCast, x: int64) = bigint x
static member inline (=>) (BigIntCast, x: uint64) = bigint x
static member inline (=>) (BigIntCast, x: bigint) = x
static member inline (=>) (BigIntCast, x: single) = bigint x
static member inline (=>) (BigIntCast, x: double) = bigint x
static member inline (=>) (BigIntCast, x: decimal) = bigint x
static member inline (=>) (BigIntCast, x: byte[]) = bigint x
type NumericCast = NumericCast with
static member inline (=>) (NumericCast, _: sbyte) = sbyte
static member inline (=>) (NumericCast, _: byte) = byte
static member inline (=>) (NumericCast, _: int16) = int16
static member inline (=>) (NumericCast, _: uint16) = uint16
static member inline (=>) (NumericCast, _: int) = int
static member inline (=>) (NumericCast, _: uint32) = uint32
static member inline (=>) (NumericCast, _: int64) = int64
static member inline (=>) (NumericCast, _: uint64) = int64
static member inline (=>) (NumericCast, _: nativeint) = nativeint
static member inline (=>) (NumericCast, _: unativeint) = unativeint
static member inline (=>) (NumericCast, _: single) = single
static member inline (=>) (NumericCast, _: double) = double
static member inline (=>) (NumericCast, _: decimal) = decimal
static member inline (=>) (NumericCast, _: bigint) = (=>) BigIntCast
let inline (^>) t x = (NumericCast => x) t
Now the inc
function can be written as:
> let inline inc x = x + (1 ^> x);;
val inline inc :
x: ^a -> ^c
when ( ^a or ^b) : (static member ( + ) : ^a * ^b -> ^c) and
(NumericCast or ^a) : (static member ( => ) : NumericCast * ^a ->
int -> ^b)
1 ^> x
reads "cast 1
to the same type as x
."
The results are the same as before:
> inc 1;;
val it : int = 2
> inc 1L;;
val it : int64 = 2L
> inc 1I;;
val it : System.Numerics.BigInteger = 2
Type safety is enforced by the compiler:
> inc "1";;
inc "1";;
^^^
C:\Users\junyoung\AppData\Local\Temp\stdin(8,1): error FS0043: No overloads match for method 'op_EqualsGreater'. The available overloads are shown below (or in the Error List window).
Possible overload: 'static member NumericCast.( => ) : NumericCast:NumericCast * x: ^t -> ('a -> 'a) when ^t : (static member get_One : -> ^t)'. Type constraint mismatch. The type
string
is not compatible with type
'a
... snip ...
In summary, it's easy to use, fast and type-safe. Choose any three. :-)
What do you think?
The original idea of using an operator trick came from here.