So our journey into F# continues. The next couple of posts will be about F# types that may or may not have standard .NET equivalents. This post will be talking about tuples.
What Are Tuples
A tuple is a grouping of unnamed but ordered values, possibly of different types.
Creating Tuples
Tuples are very easy to create, we simply do something like the following. Notice how I have created a mixed bag of tuples here, some are Int
s, and others are string
s, and we can also mix and match both the types and the number of values.
let a = (1,2)
let b = (1,"cat")
let c = (1,"cat")
let d = (1,"cat", 'c')
Generic Tuples
Functions accepting tuples can accept generic tuples without any issues as well. The F# type inference system will happily deal with deducing the correct types for the tuple. Here is an example:
let someFunction tup =
let (x,y) = tup
printfn "x is %A and y is %A" x y
do someFunction ("cat","dog")
do someFunction (11,12)
And here are the results of running the above, where it can be seen that the someFunction
function had no problem accepting and dealing with different typed tuples.
Tuple Signatures
Up until now. we have not touched on understanding F# signatures at all yet, it is in fact a topic I have decided to dedicate a whole blog post too, as I feel it is sufficiently complicated enough to warrant its own blog post. We are however where we are, which is the here and now, and we are exploring tuples, so for now I just wanted to demonstrate what a tuple signature would look like.
So let's say I declare the following tuples in a FSI window:
let a = (1,2)
let b = (1,"codfather")
let c = (1,"c", 12.5)
And then I evaluated them in the FSI window, we would see something like this:
val a : int * int = (1, 2)
val b : int * string = (1, "codfather")
val c : int * string * float = (1, "c", 12.5)
This is interesting, we can see a couple of things here, namely:
- The round braces are not part of the type signature
- The F# type system is able to correctly infer the type based on the values contained in the tuple itself
- The comma is replaced with a “*”
So just so are crystal clear, a tuple which looks like this:
let a = (1,2)
Will have type signature of:
int * int
Exploding Tuples
So we have seen how we can create tuples, but what about exploding or deconstructing them back into individual values. Is that possible? Yeah, sure it is. As before, let's start by looking at some examples:
let (a,b) = (1,2)
printfn "(a,b) = (1,2), so value of 'a' should be 1,
and it is =%i,\r\n 'b' should be 2, and it is =%i" a b
let (_,z) = (1,2)
printfn "grabbing last value from (1,2) which is = %i" z
let (a,b :string) = (1,"cat")
printfn "grabbing (1,\"cat\") which has values = %i %s" a b
let (a :int,b :string) = (1,"cat")
printfn "grabbing (1,\"cat\") which has values = %i %s" a b
let (a ,b, c) = (1,"cat", 'c')
printfn "grabbing (1,\"cat\",'c') which has values = %i %s %c" a b c
let first = fst (1, 2)
printfn "grabbing fst from (1,2) which has values = %i" first
let second = snd (1, 2)
printfn "grabbing 2nd from (1,2) which has values = %i" second
Where the results are printing to a standard Console window, as follows:
Using Let
So that was the output, but how did we get the individual parts? Well everything you need is in the code above, but let's go through one example. Suppose we had a tuple like this:
(1,2)
And I wanted to get the values of both the tuple values bound to some new individual values, we could just do this:
let (a,b) = (1,2)
We can also choose to only grab the values we truly care about, which is done using a wildcard for the unwanted parts. Which makes sure that no unnecessary value binding occurs. Here is an example:
let (_,z) = (1,2)
Using Inbuilt Tuple Functions
There is also inbuilt support for obtaining the first and second values from a tuple. Which can be done using the “fst
” and “snd
” functions. There is no support for anything other than the 2st 2
(these are probably the most common cases). “fst
” and “2nd
” can be used as follows:
let first = fst (1, 2)
let second = snd (1, 2)
Now I want to draw your attention to a special case, which is when we may have a mismatch with the number of value that we are attempting to explode into individual values. So that would be something like the example here:
let (a ,b) = (1,"cat", 'c')
You can see that the tuple itself actually contains 3 values, but the Let
binding only has 2 values, so the compiler warns us about this, as you can see in the screen shot below:
Creating New Tuples
You may want to create new tuples from existing tuples, this is easy enough, here is an example:
let oldOne = (1,2)
let (x,y) = oldOne
let newOne = (x+1,y+1)
printfn "original = %A, and we did this (x+1,y+1)\r\n to obtain newOne = %A" oldOne newOne
Which gives this output:
Tuple Equality
Tuples are ONLY considered to be equal if:
- they have the same number of values
- ALL the values are considered equal (Obviously, this could include custom
Equals
methods, or custom IEquatable
implementations, etc.)
Let's see some dead simple example, shall we.
printfn "(1,2) = (3,4) = %b" ((1,2) = (3,4))
printfn "(1,2) = (1,2) = %b" ((1,2) = (1,2))
printfn "('a','b') = ('a','b') = %b" (('a','b') = ('a','b'))
printfn "('a','b') = ('a','c') = %b" (('a','b') = ('a','c'))
which results in this:
In fact, if your tuples have different lengths and you are attempting to compare them using the equals operator “=
”, you will get a warning: