I found that:
abs -10
abs -10L
both work. So I wondered how F# implemented this and did a search in the source code:
type AbsDynamicImplTable<'T>() =
let AbsDynamic x = AbsDynamicImplTable<_>.Result x
[<CompiledName("Abs")>]
let inline abs (x: ^T) : ^T =
AbsDynamic x
when ^T : ^T = absImpl x
And I am confused with these.
As I know in a function like abs
, we must compare the input with 0, and there are different 0s for different types.
Thanks.
Returns the absolute value of a number. The absolute value of a number is the number without its sign.
ABS returns the absolute value of n . This function takes as an argument any numeric datatype or any nonnumeric datatype that can be implicitly converted to a numeric datatype. The function returns the same datatype as the numeric datatype of the argument.
To add some explanation to the code posted by ChaosPandion, the problem with F# functions like abs
is that they can work with any numeric type. There is no way to express this just using F#/.NET generics - the only supported constrains are that type parameter implements a certain interface or has a constructor, but there is no constraint for numeric types.
So, F# also supports static constraints (the type parameter is ^a
instead of usual 'a
) and these are processed at compile time using inlining (this also explains why the function has to be inline
). You can write you own function with static constraints by using built-in functions from LanguagePrimitives
which contains many useful functions that require some constraints:
> let inline half (num: ^a) : ^a =
LanguagePrimitives.DivideByInt< (^a) > num 2
;;
val inline half : ^a -> ^a
when ^a : (static member DivideByInt : ^a * int -> ^a)
> half 42.0;;
val it : float = 21.0
> half 42.0f;;
val it : float32 = 21.0f
Note that constraints are inferred - DivideByInt
requires that the type has DivideByInt
member, so our function requires the same thing (and it will work with your own type if it has this member too, which is quite useful!).
In addition to this, the implementation of abs
uses two additional tricks that are allowed only in the F# library - it specifies different code (to be used when inlining) for different types (using when ^a:int = ....
) and a fallback case, which uses Abs
member, so it will work with any explicitly listed type or a type with Abs
member. Another trick is the retype
function, which "changes the type", but doesn't contain any code - the only purpose is to make the code type-check, but this could be very unsafe - so this is used only in F# libraries.
Actually that Abs table will call this:
let inline abs_impl (x: ^a) : ^a =
(^a: (static member Abs : ^a -> ^a) (x))
when ^a : int32 = let x : int32 = retype x in if x >= 0 then x else -x
when ^a : float = let x : float = retype x in if x >= 0.0 then x else -x
when ^a : float32 = let x : float32 = retype x in if x >= 0.0f then x else -x
when ^a : int64 = let x : int64 = retype x in if x >= 0L then x else -x
when ^a : nativeint = let x : nativeint = retype x in if x >= 0n then x else -x
when ^a : int16 = let x : int16 = retype x in if x >= 0s then x else -x
when ^a : sbyte = let x : sbyte = retype x in if x >= 0y then x else -x
when ^a : decimal = System.Math.Abs(retype x : decimal)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With