Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use of `inline` in F#

Tags:

types

inline

f#

The inline keyword in F# seems to me to have a somewhat different purpose than what I'm used to in e.g. C. For example, it seems to affect a function's type (what are "statically resolved type parameters"? Aren't all F# types resolved statically?)

When should I be using inline functions?

like image 986
J Cooper Avatar asked Sep 20 '10 19:09

J Cooper


People also ask

What is the use of inline function?

An inline function is one for which the compiler copies the code from the function definition directly into the code of the calling function rather than creating a separate set of instructions in memory. This eliminates call-linkage overhead and can expose significant optimization opportunities.

What is an inline function F#?

Inline functions are functions that are integrated directly into the calling code.


2 Answers

The inline keyword indicates that a function definition should be inserted inline into any code which uses it. Most of the time, this will not have any effect on the type of the function. However, in rare cases, it can lead to a function which has a more general type, since there are constraints which cannot be expressed in the compiled form of the code in .NET, but which can be enforced when the function is being inlined.

The primary case where this applies is the use of operators.

let add a b = a + b 

will have a monomorphic inferred type (probably int -> int -> int, but it could be something like float -> float -> float if you have code which uses this function at that type instead). However, by marking this function inline, the F# compiler will infer a polymorphic type:

let inline add a b = a + b // add has type ^a ->  ^b ->  ^c when ( ^a or  ^b) : (static member ( + ) :  ^a *  ^b ->  ^c) 

There is no way to encode this type constraint in a first class way in compiled code on .NET. However, the F# compiler can enforce this constraint at the site where it inlines the function, so that all operator uses are resolved at compile time.

The type parameters ^a, ^b, and ^c are "statically resolved type parameters", which means that the types of the arguments must be statically known at the site where those parameters are being used. This is in contrast to normal type parameters (e.g. 'a, 'b, etc.), where the parameters mean something like "some type which will be supplied later, but which can be anything".

like image 168
kvb Avatar answered Oct 13 '22 08:10

kvb


You should use inline when you need to define a function that must have its type (re)evaluated at the site of each usage, as opposed to a normal function, which will have its type evaluated (inferred) only at the site of first usage, and then be regarded as being statically typed with that first inferred type signature everywhere else thereafter.

In the inline case, the function definition is effectively generic/ polymorphic, whereas in the normal (none-inline) case, the function is statically (and often implicitly) typed.

So, if you use inline, the following code:

let inline add a b = a + b  [<EntryPoint>] let main args =       let one = 1     let two = 2     let three = add one two     // here add has been compiled to take 2 ints and return an int      let dog = "dog"     let cat = "cat"     let dogcat = add dog cat     // here add has been compiled to take 2 strings and return a string      printfn "%i" three     printfn "%s" dogcat         0 

will compile, build and run to produce the following output:

3   dogcat 

In other words, the same add function definition has been used to produce both a function that adds two integers, and a function that concatenates two strings (in fact the underlying operator overloading on + is also achieved under the hood using inline).

Whereas this code, identical except that the add function is no longer declared inline:

let add a b = a + b  [<EntryPoint>] let main args =       let one = 1     let two = 2     let three = add one two     // here add has been compiled to take 2 ints and return an int      let dog = "dog"     let cat = "cat"     let dogcat = add dog cat     // since add was not declared inline, it cannot be recompiled     // and so we now have a type mismatch here      printfn "%i" three     printfn "%s" dogcat         0 

will NOT compile, failing with this complaint:

    let dogcat = add dog cat                      ^^^ - This expression was expected to have type int                            but instead has type string 

A good example of where using inline is appropriate, is when you want to define a high order function (HOF, i.e. a function taking (other) functions as arguments), e.g. a generic function to reverse the order of the application of arguments of a function with 2 arguments, e.g.

let inline flip f x y = f y x 

as is done in the answer from @pad to this question Different argument order for getting N-th element of Array, List or Seq.

like image 39
david.barkhuizen Avatar answered Oct 13 '22 07:10

david.barkhuizen