So, our journey to F # continues. The following article will focus on F # types that may or may not have standard .NET equivalents. This post is about tuples.
What are tuples
A tuple is a group of unnamed, but ordered values, possibly of different types.
Creating Tuples
Tuples are very easy to create, we just do something like the one shown below. Notice how I created a mixed package of tuples, some of which are numbers and others are strings, and we can also mix and match both types and the number of values.
let a = (1,2) let b = (1,"cat") let c = (1,"cat") let d = (1,"cat", 'c')
Universal tuples
Functions that accept tuples can also accept universal tuples without any problems. An inference system like F # will happily handle the inference of the correct types for a tuple. Here is an example.
let someFunction tup = let (x,y) = tup printfn "x is %A and y is %A" xy do someFunction ("cat","dog") do someFunction (11,12)
And here are the results of the execution above, where you can see that the someFunction function accepted and worked with various typed tuples without problems.
Tuple Signatures
So far, we have not touched upon the understanding of F # signatures at all, in fact, this is a topic to which I also decided to devote a whole blog post, since I believe that it is quite complicated. However, we are located where we are, that is, here and now, and we are studying tuples, so now I just wanted to demonstrate what the signature of the tuple would look like.
Suppose I declared the following tuples in the FSI window:
let a = (1,2) let b = (1,"codfather") let c = (1,"c", 12.5)
And then I examined them in the FSI window; we will 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:
- Parentheses are not part of a type signature
- The F # type system can correctly infer a type based on the values contained in the tuple itself.
- The comma is replaced with "*"
So the crystal clear tuple looks like this:
let a = (1,2)
Will have the following signature:
int * int
Tuple Breakdown
So, we saw how we can create tuples, but what about breaking or deconstructing them back into separate values. Is it possible? Yes of course. As before, let's start with a few 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" ab // , , , // 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" ab let (a :int,b :string) = (1,"cat") printfn "grabbing (1,\"cat\") which has values = %i %s" ab let (a ,b, c) = (1,"cat", 'c') printfn "grabbing (1,\"cat\",'c') which has values = %i %s %c" abc 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
The results are printed in a standard console window, as follows:
Using Let
But how did we get the individual parts? Well, all you need is the code above, but let's look at one example. Suppose we had a tuple like this:
(1,2)
And I wanted to get the values of both "values" of the tuple, tied to some new individual values, we could just do this:
let (a,b) = (1,2)
We can also select only those values that we really need, which is done using a wildcard for unwanted parts. Which guarantees the absence of unnecessary value binding. Here is an example:
let (_,z) = (1,2)
Using the built-in tuple functions
There is also built-in support for getting the first and second values from a tuple. What can be done with the
fst and
snd functions. There is no support for anything other than the 2nd element (these are probably the most common cases). “
Fst ” and “
snd ” 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 where we may have a mismatch with the number of values that we are trying to explode into individual values. So this will be something like an example here:
let (a ,b) = (1,"cat", 'c')
You can see that the tuple itself actually contains 3 values, but the Let binding has only 2 values, so the compiler warns us about this, as you can see in the screenshot below:
Creating New Tuples
You might want to create new tuples from existing tuples, it's simple 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
Comparing Tuples
Tuples are considered equal only if
- They have the same number of values.
- ALL values are considered equal (obviously, these can be custom Equals methods or custom IEquatable implementations and the like)
Let's look at some simple example.
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'))
In fact, if your tuples have different lengths (the number of elements) and you try to compare them using the equality operator “=”, you will get a warning:
Tuple matching patterns
We have not dealt with matching patterns yet, but later we will see a whole post on this topic. In the meantime, just know that this is a way to match input parameters again. You can do this for tuples, which is done as follows:
let someFunction tup = match tup with | 1,2 -> printfn "you passed (1,2)" | _,_ -> printfn "you passed some other tuple" do someFunction (11,12) do someFunction (4,5) do someFunction (1,2) do someFunction (13,23)