F# for Fun and Profit
  • Introduction
  • Getting started
    • Contents of the book
    • "Why use F#?" in one page
    • Installing and using F#
    • F# syntax in 60 seconds
    • Learning F#
    • Troubleshooting F#
    • Low-risk ways to use F# at work
      • Twenty six low-risk ways to use F# at work
      • Using F# for development and devops scripts
      • Using F# for testing
      • Using F# for database related tasks
      • Other interesting ways of using F# at work
  • Why use F#?
    • The "Why use F#?" Series
      • Introduction to the 'Why use F#' series
      • F# syntax in 60 seconds
      • Comparing F# with C#: A simple sum
      • Comparing F# with C#: Sorting
      • Comparing F# with C#: Downloading a web page
      • Four Key Concepts
      • Conciseness
      • Type inference
      • Low overhead type definitions
      • Using functions to extract boilerplate code
      • Using functions as building blocks
      • Pattern matching for conciseness
      • Convenience
      • Out-of-the-box behavior for types
      • Functions as interfaces
      • Partial Application
      • Active patterns
      • Correctness
      • Immutability
      • Exhaustive pattern matching
      • Using the type system to ensure correct code
      • Worked example: Designing for correctness
      • Concurrency
      • Asynchronous programming
      • Messages and Agents
      • Functional Reactive Programming
      • Completeness
      • Seamless interoperation with .NET libraries
      • Anything C# can do...
      • Why use F#: Conclusion
  • Thinking Functionally
    • The "Thinking Functionally" Series
      • Thinking Functionally: Introduction
      • Mathematical functions
      • Function Values and Simple Values
      • How types work with functions
      • Currying
      • Partial application
      • Function associativity and composition
      • Defining functions
      • Function signatures
      • Organizing functions
      • Attaching functions to types
      • Worked example: A stack based calculator
  • Understanding F# ###
    • The "Expressions and syntax" Series
      • Expressions and syntax: Introduction
      • Expressions vs. statements
      • Overview of F# expressions
      • Binding with let, use, and do
      • F# syntax: indentation and verbosity
      • Parameter and value naming conventions
      • Control flow expressions
      • Exceptions
      • Match expressions
      • Formatted text using printf
      • Worked example: Parsing command line arguments
      • Worked example: Roman numerals
    • The "Understanding F# types" Series
      • Understanding F# types: Introduction
      • Overview of types in F#
      • Type abbreviations
      • Tuples
      • Records
      • Discriminated Unions
      • The Option type
      • Enum types
      • Built-in .NET types
      • Units of measure
      • Understanding type inference
    • Choosing between collection functions
    • The "Object-oriented programming in F#" Series
      • Object-oriented programming in F#: Introduction
      • Classes
      • Inheritance and abstract classes
      • Interfaces
      • Object expressions
    • The "Computation Expressions" Series
      • Computation expressions: Introduction
      • Understanding continuations
      • Introducing 'bind'
      • Computation expressions and wrapper types
      • More on wrapper types
      • Implementing a builder: Zero and Yield
      • Implementing a builder: Combine
      • Implementing a builder: Delay and Run
      • Implementing a builder: Overloading
      • Implementing a builder: Adding laziness
      • Implementing a builder: The rest of the standard methods
    • Organizing modules in a project
    • The "Dependency cycles" Series
      • Cyclic dependencies are evil
      • Refactoring to remove cyclic dependencies
      • Cycles and modularity in the wild
    • The "Porting from C#" Series
      • Porting from C# to F#: Introduction
      • Getting started with direct porting
  • Functional Design ###
    • The "Designing with types" Series
      • Designing with types: Introduction
      • Single case union types
      • Making illegal states unrepresentable
      • Discovering new concepts
      • Making state explicit
      • Constrained strings
      • Non-string types
      • Designing with types: Conclusion
    • Algebraic type sizes and domain modelling
    • Thirteen ways of looking at a turtle
      • Thirteen ways of looking at a turtle (part 2)
      • Thirteen ways of looking at a turtle - addendum
  • Functional Patterns ###
    • How to design and code a complete program
    • A functional approach to error handling (Railway oriented programming)
      • Railway oriented programming: Carbonated edition
    • The "Understanding monoids" Series
      • Monoids without tears
      • Monoids in practice
      • Working with non-monoids
    • The "Understanding Parser Combinators" Series
      • Understanding Parser Combinators
      • Building a useful set of parser combinators
      • Improving the parser library
      • Writing a JSON parser from scratch
    • The "Handling State" Series
      • Dr Frankenfunctor and the Monadster
      • Completing the body of the Monadster
      • Refactoring the Monadster
    • The "Map and Bind and Apply, Oh my!" Series
      • Understanding map and apply
      • Understanding bind
      • Using the core functions in practice
      • Understanding traverse and sequence
      • Using map, apply, bind and sequence in practice
      • Reinventing the Reader monad
      • Map and Bind and Apply, a summary
    • The "Recursive types and folds" Series
      • Introduction to recursive types
      • Catamorphism examples
      • Introducing Folds
      • Understanding Folds
      • Generic recursive types
      • Trees in the real world
    • The "A functional approach to authorization" Series
      • A functional approach to authorization
      • Constraining capabilities based on identity and role
      • Using types as access tokens
  • Testing
    • An introduction to property-based testing
    • Choosing properties for property-based testing
  • Examples and Walkthroughs
    • Worked example: Designing for correctness
    • Worked example: A stack based calculator
    • Worked example: Parsing command line arguments
    • Worked example: Roman numerals
    • Commentary on 'Roman Numerals Kata with Commentary'
    • Calculator Walkthrough: Part 1
      • Calculator Walkthrough: Part 2
      • Calculator Walkthrough: Part 3
      • Calculator Walkthrough: Part 4
    • Enterprise Tic-Tac-Toe
      • Enterprise Tic-Tac-Toe, part 2
    • Writing a JSON parser from scratch
  • Other
    • Ten reasons not to use a statically typed functional programming language
    • Why I won't be writing a monad tutorial
    • Is your programming language unreasonable?
    • We don't need no stinking UML diagrams
    • Introvert and extrovert programming languages
    • Swapping type-safety for high performance using compiler directives
Powered by GitBook
On this page
  • Generic tuples
  • Tuples of complex types
  • Key points about tuples
  • Making and matching tuples
  • Using tuples in practice
  • Using tuples for returning multiple values
  • Creating tuples from other tuples
  • Equality
  • Tuple representation

Was this helpful?

  1. Understanding F# ###
  2. The "Understanding F# types" Series

Tuples

Multiplying types together

PreviousType abbreviationsNextRecords

Last updated 5 years ago

Was this helpful?

We're ready for our first extended type -- the tuple.

Let's start by stepping back again and looking at a type such as "int". As we hinted at before, rather than thinking of "int" as a abstract thing, you can think of it as concrete collection of all its possible values, namely the set {...,-3, -2, -1, 0, 2, 3, ...}.

So next, imagine two copies of this "int" collection. We can "multiply" them together by taking the Cartesian product of them; that is, making a new list of objects by picking every possible combination of the two "int" lists, as shown below:

As we have already seen, these pairs are called tuples in F#. And now you can see why they have the type signature that they do. In this example, the "int times int" type is called "int * int", and the star symbol means "multiply" of course! The valid instances of this new type are all the pairs: (-2,2),(-1,0), (2,2) and so on.

Let's see how they might be used in practice:

let t1 = (2,3)
let t2 = (-2,7)

Now if you evaluate the code above you will see that the types of t1 and t2 are int*int as expected.

val t1 : int * int = (2, 3)
val t2 : int * int = (-2, 7)

This "product" approach can be used to make tuples out of any mixture of types. Here is one for "int times bool".

And here is the usage in F#. The tuple type above has the signature "int*bool".

let t3 = (2,true)
let t4 = (7,false)

// the signatures are:
val t3 : int * bool = (2, true)
val t4 : int * bool = (7, false)

Strings can be used as well, of course. The universe of all possible strings is very large, but conceptually it is the same thing. The tuple type below has the signature "string*int".

Test the usage and signatures:

let t5 = ("hello",42)
let t6 = ("goodbye",99)

// the signatures are:
val t5 : string * int = ("hello", 42)
val t6 : string * int = ("goodbye", 99)

And there is no reason to stop at multiplying just two types together. Why not three? Or four? For example, here is the type int * bool * string.

Test the usage and signatures:

let t7 = (42,true,"hello")

// the signature is:
val t7 : int * bool * string = (42, true, "hello")

Generic tuples

Generics can be used in tuples too.

The usage is normally associated with functions:

let genericTupleFn aTuple = 
   let (x,y) = aTuple
   printfn "x is %A and y is %A" x y

And the function signature is:

val genericTupleFn : 'a * 'b -> unit

which means that "genericTupleFn" takes a generic tuple ('a * 'b) and returns a unit

Tuples of complex types

Any kind of type can be used in a tuple: other tuples, classes, function types, etc. Here are some examples:

// define some types
type Person = {First:string; Last:string}
type Complex = float * float
type ComplexComparisonFunction = Complex -> Complex -> int

// define some tuples using them
type PersonAndBirthday = Person * System.DateTime
type ComplexPair = Complex * Complex
type ComplexListAndSortFunction = Complex list * ComplexComparisonFunction
type PairOfIntFunctions = (int->int) * (int->int)

Key points about tuples

Some key things to know about tuples are:

  • A particular instance of a tuple type is a single object, similar to a two-element array in C#, say. When using them with functions they count as a single parameter.

  • Tuple types cannot be given explicit names. The "name" of the tuple type is determined by the combination of types that are multiplied together.

  • The order of the multiplication is important. So int*string is not the same tuple type as string*int.

  • The comma is the critical symbol that defines tuples, not the parentheses. You can define tuples without the parentheses, although it can sometimes be confusing. In F#, if you see a comma, it is probably part of a tuple.

These points are very important -- if you don't understand them you will get confused quite quickly!

// a function that takes a single tuple parameter 
// but looks like it takes two ints
let addConfusingTuple (x,y) = x + y

Making and matching tuples

The tuple types in F# are somewhat more primitive than the other extended types. As you have seen, you don't need to explicitly define them, and they have no name.

It is easy to make a tuple -- just use a comma!

let x = (1,2)                 
let y = 1,2        // it's the comma you need, not the parentheses!      
let z = 1,true,"hello",3.14   // create arbitrary tuples as needed

And as we have seen, to "deconstruct" a tuple, use the same syntax:

let z = 1,true,"hello",3.14   // "construct"
let z1,z2,z3,z4 = z           // "deconstruct"

When pattern matching like this, you must have the same number of elements, otherwise you will get an error:

let z1,z2 = z     // error FS0001: Type mismatch. 
                  // The tuples have differing lengths

If you don't need some of the values, you can use the "don't care" symbol (the underscore) as a placeholder.

let _,z5,_,z6 = z     // ignore 1st and 3rd elements

As you might guess, a two element tuple is commonly called a "pair" and a three element tuple is called a "triple" and so on. In the special case of pairs, there are functions fst and snd which extract the first and second element.

let x = 1,2
fst x
snd x

They only work on pairs. Trying to use fst on a triple will give an error.

let x = 1,2,3
fst x              // error FS0001: Type mismatch. 
                   // The tuples have differing lengths of 2 and 3

Using tuples in practice

Tuples have a number of advantages over other more complex types. They can be used on the fly because they are always available without being defined, and thus are perfect for small, temporary, lightweight structures.

Using tuples for returning multiple values

It is a common scenario that you want to return two values from a function rather than just one. For example, in the TryParse style functions, you want to return (a) whether the value was parsed and (b) if parsed, what the parsed value was.

Here is an implementation of TryParse for integers (assuming it did not already exist, of course):

let tryParse intStr = 
   try
      let i = System.Int32.Parse intStr
      (true,i)
   with _ -> (false,0)  // any exception

//test it
tryParse "99"
tryParse "abc"

Here's another simple example that returns a pair of numbers:

// return word count and letter count in a tuple
let wordAndLetterCount (s:string) = 
   let words = s.Split [|' '|]
   let letterCount = words |> Array.sumBy (fun word -> word.Length ) 
   (words.Length, letterCount)

//test
wordAndLetterCount "to be or not to be"

Creating tuples from other tuples

As with most F# values, tuples are immutable and the elements within them cannot be assigned to. So how do you change a tuple? The short answer is that you can't -- you must always create a new one.

Say that you need to write a function that, given a tuple, adds one to each element. Here's an obvious implementation:

let addOneToTuple aTuple =
   let (x,y,z) = aTuple
   (x+1,y+1,z+1)   // create a new one

// try it
addOneToTuple (1,2,3)

This seems a bit long winded -- is there a more compact way? Yes, because you can deconstruct a tuple directly in the parameters of a function, so that the function becomes a one liner:

let addOneToTuple (x,y,z) = (x+1,y+1,z+1)

// try it
addOneToTuple (1,2,3)

Equality

Tuples have an automatically defined equality operation: two tuples are equal if they have the same length and the values in each slot are equal.

(1,2) = (1,2)                      // true
(1,2,3,"hello") = (1,2,3,"bye")    // false
(1,(2,3),4) = (1,(2,3),4)          // true

Trying to compare tuples of different lengths is a type error:

(1,2) = (1,2,3)                    // error FS0001: Type mismatch

And the types in each slot must be the same as well:

(1,2,3) = (1,2,"hello")   // element 3 was expected to have type
                          // int but here has type string    
(1,(2,3),4) = (1,2,(3,4)) // elements 2 & 3 have different types

Tuples also have an automatically defined hash value based on the values in the tuple, so that tuples can be used as dictionary keys without problems.

(1,2,3).GetHashCode()

Tuple representation

(1,2,3).ToString()

And it is worth re-iterating the point made in : don't mistake tuples for multiple parameters in a function.

And as noted in a , tuples have a nice default string representation, and can be serialized easily.

previous posts
previous post
int*int tuple
int*bool tuple
string*int tuple
int*bool*string tuple
'a*'b tuple