United Kingdom: +44 (0)208 088 8978

Ignoring explicitly with type parameters

Learn how to improve type safety of your F# apps by using type parameters on ignore

If an expression returns a value, you'll probably want to do something with it. F# helps you make sure you do so by giving a warning if you don't:

let sendMessage message =
    let messageId = Guid.NewGuid()
    printfn $"Sending message '%s{message}'; message ID: {messageId}"

    messageId

sendMessage "Hello World"
//The result of this expression has type 'System.Guid' and is implicitly ignored. Consider using 'ignore' to discard this value explicitly, e.g. 'expr |> ignore', or 'let' to bind the result to a name, e.g. 'let result = expr'

But sometimes you really don't want to do anything with the returned value, for example if you run a function just for it's side effect. As mentioned by the warning, the ignore function, with signature T -> unit, allows you to explicitly ignore values, getting rid of the warning:

sendMessage "Hello World" |> ignore

This is great, because it makes it clear that we did not just forget about the value, but explicitly don't want to use it. There's a subtle risk in using this though; take the following situation, where we added a second argument to the sendMessage function:

let sendMessage message recipient =
    let messageId = Guid.NewGuid()
    printfn $"Sending message '%s{message}' to %s{recipient}; message ID: {messageId}"

    messageId

sendMessage "Hello World!" |> ignore

If you run this code, nothing will be printed; instead of calling the sendMessage function, we partially apply it. If you remove the ignore function, a new warning is revealed:

This expression is a function value, i.e. is missing arguments. Its type is string -> System.Guid.

Fortunately, there's a simple way to avoid this; like any generic function, ignore can take a type parameter; this means we'll get a compiler error when we pass the wrong type to it:

sendMessage "Hello World" |> ignore<Guid>
// Type mismatch. Expecting a
//     '(string -> Guid) -> 'a'    
// but given a
//     'Guid -> unit'    
// The type 'Guid' does not match the type 'string -> Guid'

sendMessage "Hello World" "John Doe" |> ignore<Guid>

I'd recommend adding a type parameter any time you use the ignore function; it can save you a lot of headache when functions all of a sudden stop being executed after refactoring. By explicitly specifying the expected return type, you ensure that the ignore function receives the correct value and that the side-effecting function is indeed fully applied before its result is discarded. This practice adds a layer of safety, especially in larger codebases where subtle changes can lead to unexpected behavior.

Another way to get rid of this warning is binding it to _. This method is also sensitive to ignoring a partially applied function!