Tuple vs * star types in F#

Viewed 121

Consider the following code:

let pair = System.Tuple.Create (10, "foo")       // val pair : int * string = (10, "foo")
let tuple = System.Tuple.Create <| (10, "foo")   // val tuple : System.Tuple<int * string> = ((10, "foo"))
  1. Why doesn't the two lines yield values of the same type? Does the type of the argument (10, "foo") somehow change between the two lines?
  2. What's the exact difference between int * string and System.Tuple<int * string>?

For 2, at least the latter has null as a value (this is how this question came up). Are there other differences?

2 Answers

There are two different overloads of Tuple.Create:

 Tuple.Create<'T1>(item1: 'T1)
 Tuple.Create<'T1, 'T2>(item1: 'T1, item2: 'T2)
 

In the first case you just calling a method with two arguments. So the second Tuple.Create overload is obviously picked. No surprise.

But with piping you first create a tuple instance. And then pass it to Tuple.Create method. This is what happens in the second example

  let intermediate : Tuple<int, string> = (10, "foo")
  let tuple = Tuple.Create(intermediate)

With a single argument the first Tuple.Create overload will be picked.

Note: star type is a way tuple type names are written in F#. So Tuple<int, string, bool> will be (int * string * bool). It's the same thing.

Your tuple is a tuple of one (1) element, namely the tuple 10,"foo". It is equivalent of

System.Tuple.Create(System.Tuple.Create(10, "foo"))

Your pair on the other hand is a tuple of the two elements 10 and "foo".

So pair has type System.Tuple<int,string> (which is the same as int * string), but tuple has type System.Tuple<System.Tuple<int,string>> (which is System.Tuple<int * string>)

Related