layout: post
title: “Type inference”
description: “How to avoid getting distracted by complex type syntax”
nav: why-use-fsharp
seriesId: “Why use F#?”
seriesOrder: 8

categories: [Conciseness,Types]

As you have already seen, F# uses a technique called “type inference” to greatly reduce the number of type annotations that need to be explicitly specified in normal code. And even when types do need to be specified, the syntax is less longwinded compared to C#.

To see this, here are some C# methods that wrap two standard LINQ functions. The implementations are trivial, but the method signatures are extremely complex:

  1. public IEnumerable<TSource> Where<TSource>(
  2. IEnumerable<TSource> source,
  3. Func<TSource, bool> predicate
  4. )
  5. {
  6. //use the standard LINQ implementation
  7. return source.Where(predicate);
  8. }
  9. public IEnumerable<IGrouping<TKey, TSource>> GroupBy<TSource, TKey>(
  10. IEnumerable<TSource> source,
  11. Func<TSource, TKey> keySelector
  12. )
  13. {
  14. //use the standard LINQ implementation
  15. return source.GroupBy(keySelector);
  16. }

And here are the exact F# equivalents, showing that no type annotations are needed at all!

  1. let Where source predicate =
  2. //use the standard F# implementation
  3. Seq.filter predicate source
  4. let GroupBy source keySelector =
  5. //use the standard F# implementation
  6. Seq.groupBy keySelector source

You might notice that the standard F# implementations for “filter” and “groupBy” have the parameters in exactly the opposite order from the LINQ implementations used in C#. The “source” parameter is placed last, rather than first. There is a reason for this, which will be explained in the thinking functionally series.

The type inference algorithm is excellent at gathering information from many sources to determine the types. In the following example, it correctly deduces that the list value is a list of strings.

  1. let i = 1
  2. let s = "hello"
  3. let tuple = s,i // pack into tuple
  4. let s2,i2 = tuple // unpack
  5. let list = [s2] // type is string list

And in this example, it correctly deduces that the sumLengths function takes a list of strings and returns an int.

  1. let sumLengths strList =
  2. strList |> List.map String.length |> List.sum
  3. // function type is: string list -> int