Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how are abs, sign etc implemented in F#

Tags:

inline

f#

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.

like image 491
Yin Zhu Avatar asked Feb 03 '10 15:02

Yin Zhu


People also ask

What is the use of ABS ()?

Returns the absolute value of a number. The absolute value of a number is the number without its sign.

What is the return data type of abs?

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.


2 Answers

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.

like image 173
Tomas Petricek Avatar answered Nov 23 '22 19:11

Tomas Petricek


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) 
like image 39
ChaosPandion Avatar answered Nov 23 '22 19:11

ChaosPandion