Inline Functions and SRTP
Prerequisites: Generics
.NET generics can't say "any type that has a + operator" or "any type
with a Length property". F# can, with statically resolved type
parameters (SRTP), written ^T and available in inline functions.
The compiler copies the function body into each call site and checks
the requirement for the concrete type there.
Generic math
let inline sumOfSquares xs =
xs |> List.sumBy (fun x -> x * x)
printfn "%d" (sumOfSquares [1; 2; 3])
printfn "%.2f" (sumOfSquares [0.5; 1.5])
printfn "%M" (sumOfSquares [1.1m; 2.0m])
let inline average xs =
let total = List.sum xs
LanguagePrimitives.DivideByInt total (List.length xs)
printfn "%.2f" (average [1.0; 2.0; 4.0])
printfn "%M" (average [10m; 20m])
14
2.50
5.21
2.33
15
LanguagePrimitives.DivideByInt divides any float-like type by an int,
and LanguagePrimitives.GenericZero and GenericOne give 0 and 1 of
"whatever numeric type is being used here".
Member constraints
An SRTP constraint can require any member:
let inline lengthOf (x: ^T) = (^T: (member Length: int) x)
printfn "%d" (lengthOf "hello")
printfn "%d" (lengthOf [| 1; 2; 3 |])
printfn "%d" (lengthOf [ 'a'; 'b' ])
type Box = { Label: string } with
member this.Length = this.Label.Length * 100
printfn "%d" (lengthOf { Label = "big" })
5
3
2
300
Strings, arrays, lists, and your own record all have a Length
property — they share no interface, but lengthOf accepts all of them.
Passing something without Length is a compile error.
Operators on your types
Built-in generic functions use SRTP too, so if your type defines the right static members, it works with them:
type Money =
{ Cents: int64 }
static member (+) (a: Money, b: Money) = { Cents = a.Cents + b.Cents }
static member Zero = { Cents = 0L }
override this.ToString() = sprintf "$%d.%02d" (this.Cents / 100L) (this.Cents % 100L)
let items = [ { Cents = 1999L }; { Cents = 501L }; { Cents = 250L } ]
printfn "%O" (List.sum items)
$27.50
List.sum requires + and a Zero member — Money has both.
Use sparingly
inlinemakes every call site bigger and error messages harder to read. Use it for small, genuinely generic numeric or utility functions.- For most "works with several types" needs, interfaces, plain generics, or passing a function parameter are simpler.
inlinealso helps performance when a small function takes a lambda (the lambda can be inlined too), which is why manyArrayfunctions are inline.