Search code examples
f#inlineactive-pattern

Why would I use the keyword "inline" for an Active Pattern?


I still cannot understand why I would use the keyword inline for a function.

What does it give me that I don't already have?

let inline (|Positive|Neutral|Negative|) x =
 match sign x with
 | 1 -> Positive
 | -1 -> Negative
 | _ -> Neutral

Solution

  • In this case, it may be easier to understand what inline gives you if you try to remove the keyword:

    let (|Positive|Neutral|Negative|) x =
        match sign x with
        | 1 -> Positive
        | -1 -> Negative
        | _ -> Neutral
    

    This active pattern has the type float -> Choice<unit,unit,unit>. Notice that the compiler has inferred that it only works for float input.

    The consequences of this may be most apparent if we also define a function that uses this pattern, e.g. one that determines if a number is a natural number:

    let isNatural = function
        | Positive -> true
        | _ -> false
    

    This function has the type float -> bool, which means that you can use it only with float input:

    > isNatural 1.;;
    val it : bool = true
    > isNatural 1;;
    
    >   isNatural 1;;
      ----------^
    
    stdin(4,11): error FS0001: This expression was expected to have type
        float    
    but here has type
        int
    

    What if you want to be able to determine that both float, int, int64, etcetera, are natural numbers? Should you duplicate these functions for all input types?

    You don't have to. You can inline the functions:

    let inline (|Positive|Neutral|Negative|) x =
        match sign x with
        | 1 -> Positive
        | -1 -> Negative
        | _ -> Neutral
    
    let inline isNatural x =
        match x with
        | Positive -> true
        | _ -> false
    

    Because of the inline keyword, the compiler keeps the type of the functions generic:

    > 
    val inline ( |Positive|Neutral|Negative| ) :
      x: ^a -> Choice<unit,unit,unit> when  ^a : (member get_Sign :  ^a -> int)
    val inline isNatural : x: ^a -> bool when  ^a : (member get_Sign :  ^a -> int)
    

    This means that you can use any type for input, as long as there exists a function get_Sign that takes that type as input, and returns int.

    You can now call the functions with both float, int, and other numeric types:

    > isNatural 1.;;
    val it : bool = true
    > isNatural 1;;
    val it : bool = true