Reader Level:
Articles

F# Tuples and Binding (and more Binding)

By Matthew Cochran on March 26, 2008
Binding in F# is similar to using variables in C# but there are some big differences. This article discusses one of the most used keywords in F# for binding ("let") and how it is different than setting a C# variable. I also cover the tuple F# data structure.
  • 0
  • 0
  • 13952

What's A Tuple?

First we have to look at a core data type in F# called a tuple.  Tuples are a way of grouping other data types.  Tuples are immutable data structures that can have any number of items.  (By immutable, I mean that it can't be "mutated" or that there are no property setters, only property getters, like a C# String object.)

The number of items we declare in a tuple probably should be kept to a manageable number (more than seven items starts to get really confusing, at least for my human brain, and I would probably consider a different data structure if I needed more than 7 or so type "slots").

Tuples are declared by surrounding a comma separated list of values in parenthesis.  The data type that results are the types separated by the '*' character.  Here is a tuple of an int and a string:

> (4, "Hello World");;
val it : int * string = (4, "Hello World")

If we look at it from a C# perspective this is the type of structure we have with a two-member tuple:

public class Tuple<T, U>
{
    public Tuple(T pValue1, U pValue2)
    {
        m_value1 = pValue1;
        m_value2 = pValue2;
    }

    private T m_value1;
    private U m_value2;

    public T Value1
    {
        get { return m_value1; }
    }

    public U Value2
    {
        get { return m_value2; }
    }
}

And we would declare an instance in C# (.NET 3.5) as follows:

Tuple<Int32, String> value =
    new Tuple<int, string>(
        4,
        "Hello World");

Of course we could have a structure with many more items

> (4, '4', "4", (fun x->x+4), 4.0, (fun x->x+ 4.0));;
val it : int * char * string * (int -> int) * float * (float -> float)
= (4, '4', "4", <fun:it@5>, 4.0, <fun:it@5_1>)

Which would be a pretty messy generic data type in C#:

public class Tuple<T, U, V, W, X, Y>
{
    public Tuple(T pValue1, U pValue2, V pValue3, W pValue4, X pValue5, Y pValue6)
    {
        m_value1 = pValue1;
        m_value2 = pValue2;
        m_value3 = pValue3;
        m_value4 = pValue4;
        m_value5 = pValue5;
        m_value6 = pValue6;
    }

    private T m_value1;
    private U m_value2;
    private V m_value3;
    private W m_value4;
    private X m_value5;
    private Y m_value6;

    public T Value1
    {
        get { return m_value1; }
    }

    public U Value2
    {
        get { return m_value2; }
    }

    public V Value3
    {
        get { return m_value3; }
    }

    public W Value4
    {
        get { return m_value4; }
    }

    public X Value5
    {
        get { return m_value5; }
    }

    public Y Value6
    {
        get { return m_value6; }
    }
}

And the declaration alone would be almost unmanagable in C#:

Tuple<Int32, Char, String, Func<Int32, Int32>, Double, Func<Double, Double>> value2 =
    new Tuple<int, char, string, Func<int, int>, double, Func<double, double>>(
        4,
        '4',
        "4",
        x => x + 4,
        4.0,
        x => x + 4.0);

One of the really nice things about F# is how concise the code is because everything is generic by default and we only declare the type if we want to (which we'll get to in a bit).

Binding Part 1.

In F# we can assign a value to a placeholder through binding.  The syntax for binding is first using the "let" keyword, followed by the placeholder name followed by an equal sign and then the value.

Here we are binding the value 4 to the placeholder x.  Notice the response telling us that the type of x is an integer.

> let x = 4;;
val x : int

This will look very familiar to C# programmers. (Note: If we use the F# interactive window, the placeholders will last until we close our Studio process or type "#quit" to reset our F# interactive session)

Now, wherever we want to use the integer "4", we can substitute our placeholder "x" as in the following example:

> x + 1;;
val it : int = 5

We can also bind to functions as in the following example:

> let f = (fun x -> x + 1);;
val f : int -> int

> f;;
val it : (int -> int) = <fun:clo@0>

> f x;;
val it : int = 5

Binding Part II.

Ok, now is where we depart from what we know in C# and can see why we don't consider F# binding the equivalent of assigning a variable some value in C#.

Remember tuples?  Well, we can bind to multiple placeholders using a tuple as follows:

> let (m,n) = (4, "Hello World");;
val m : int
val n : string

Notice that the placeholders "m" and "n" did not have to be declared before our functions but we have bound the value "4" to our placeholder "m" and the value "Hello World" to our placeholder "n" anyways.

> m;;
val it : int = 4

> n;;
val it : string = "Hello World"

Because functions are first class citizens of F#, we also have a more concise way to bind to F# function placeholders.  This is how we can declare a function with one input parameter "x"

> f x = x + 1;;

val it : bool = true

Notice how the type is inferred for us and we don't have to explicitly declare it as we would have had to do in  C#:

> f;;

val it : (int -> int) = <fun:clo@0_1>

In an earlier article I mentioned that sometimes we have to help F# figure out the type.  Let's say we wanted to build a function that added something to the end of a string:

> f x = x + " World";;

  f x = x + " World";;
  ----------^^^^^^^^^
stdin(25,10): error: FS0001: This expression has type
      string
but is here used with type
      int
stopped due to error

Right now I can already hear you thinking "Dude! I meant for you to use a string…"

Here, we get an error because the F# compiler sees a type conflict between our operation '+' which is an "int -> int" and the string " World".  Remember the syntax we saw earlier when we saw the type of the placeholder?

> let x = 4;;

val x : int

We can use the same syntax to constrain our type so our new string appending function will work by placing a colon after our input parameter followed by the type we want:

> let f x:string = x + " World";;

val f : string -> string

And now we can happily continue on our way with our new append function working as expected:

> f "Hello";;

val it : string = "Hello World"

We can also either pass tuples to functions as well as using a list of parameters.  For example, we can declare a function with two inputs as follows:

> let f (x:string) y = x + " " + y;;

val f : string -> string -> string

Notice how if we give the compiler a little bit of a hint with "x", it can figure out what type "y" is supposed to be.

And we can call our function as follows:

> f "Hello" "World";;

val it : string = "Hello World"

Similarly, we can pass a tuple as input to a function:

let f ((x:string), y) = x + " " + y;;

val f : string * string -> string

Notice how our function type is now "string * string -> string" which has a tuple ("string * string") instead of our previous "string -> string -> string" signature.  The function with a tupled argument ("string * string -> string") would be considered a function in non-curried form, while the function with no tuples ("string -> string -> string") would be considered to be curried.  In a future article I'll discuss how to move back and forth between the curried and non-curried formats and the reasons and drawbacks for doing this.

This should give you a bit of an idea of how F# binding is a bit different from C# variable assignments.

We can get values out of a tuple by defining a simple function:

> let first (x,y) = x;;

val first : 'a * 'b -> 'a

Our function type ('a * 'b -> 'a) says that if we have a tuple with something of type a in the first slot and something of type b in the second slot, we'll get something of type a as an output.

> first ("Hello", "World");;

val it : string = "Hello"

And you can see it works as expected.

Binding Part III

We can also have nested "let"s, but first let's turn on the light syntax option by typing "#light;;" in F# Interactive.  This will allow us to use an even more concise syntax where whitespace matters and makes for easier readin' code.

> #light;;

Here's another feature of the "let" binding keyword. We can "nest" lets to build more complex functions as in the following example.

>
let f x:int =
  let square m = m * m
  x |> square |> square;;

val f : int -> int

> f 2;;

val it : int = 16

(If the forward pipe operator "|>" is throwing you for a loop, check out my earlier article on the operator.)

Let's walk through what we have. 

We have obviously built a function that takes and int and returns an int (we know that from the signature displayed which is "int -> int").  This function has an internal function we bound to "square" that multiplies and integer with itself.  Notice how the compiler figures out the type of "square" from our single constraint on "x".

let f x:float =
  let square m = m *
  x |> square |> square;;

As an aside.. . we could have done the same thing and constrained the type to float and the compiler figures out what we mean:

>
let f x:float =
  let square m = m * m
  x |> square |> square;;

val f : float -> float

But I digress.

Our inner function is scoped to the outer.  Meaning that all the inner "lets" are only available to the outer "let", so "square" will not be defined outside the context of our function "f" and can't bee seen by F# interactive:

> square;;
  square;;
  ^^^^^^^
stdin(67,0): error: FS0039: The value or constructor 'square' is not defined.
stopped due to error

The last line of our function defines the output where we specify to take the input value "x" and push the result through our "square" function and take the result of that and push it through our "square" function again:

let f x:float =
  let square m = m * m
  x |> square |> square;;

And, as always, we terminate our instructions with the double semi-colon:

let f x:float =
  let square m = m * m
  x |> square |> square;;

If we look at a similar function in C# it would look like this:

public Int32 f(Int32 x)
{
    Func<Int32, Int32> square = m => m * m;
    return square(square(x));
}

If you are having problems with the lambda syntax, check out my earlier article on anonymous delegates and lambda syntax in C#.  This is a good illustration of how C# is becoming more "functional" and (to me) another good reason to wrap my mind around F# in order to take full advantage of all the opportunities available with the new C# functional language features.

We can have nested lets that go as many levels deep as we want.  If you want do drive yourself a little crazier, here's a little F# puzzle… see if you can figure out what this function will do:

let f x:int =
  let g y =
    let h z = z * z
    (h y) * (h y)
  g x;;

I'll leave this up to you (if you are really curious maybe it will get you to download F# and give it a shot).  After you figure it out, you'll see how flexible and expressive F# is which give us as developers many ways to solve problems (some more convoluted than others).

That's about it for this F# binding and tuple tour, I hope you enjoyed it.  I'll be back soon to look at more F# language features.

Until next time,
Happy coding

COMMENT USING

Trending up