In this post, we will look at F# bindings, in particular, we will look at Let
/ Use
/ Do
. Now you may be asking yourself what a binding is, and since we have not really covered it yet, now would be a good time to talk about this.
Quite simply, a binding associates an identifier with a value or function.
Let
You use the let
keyword to bind a name to a value or function. There are actually subtly different uses of Let
, where one is declared as a top level in a module, and then another is where we define some sort of local context. Here is an example of both of these:
module DemoModule =
let someFunction =
let a = 1
let b = 2
a * b
We would be able to access the someFunction
using a fully qualified name such as DemoModule.someFunction
, but the nested Let
bindings (a,b
) are only accessible to the top level Let
binding. You would typically see more cases where we are using the Let
binding to declare some inner module values, so let's concentrate our efforts there (though it is still important to know you can use Let
at module level).
So let’s have a look at a few more examples:
let aString ="this is a string"
let aInt = 12
let aDecimal = 12.444
let aPiFunction () = Math.PI
let aSquareRootFunction (x) = Math.Sqrt(x)
let aFullyTypedSquareRootFunction (x :float) = Math.Sqrt(x)
let a,b = "a","tuple"
It can be seen that we are able to use the Let
binding, to bind to numerous values, that can be of various types such as:
- Integer
- Decimal
- A Function with no input parameters
- A Function with input parameters (where the F# type inference system will correctly choose the Type)
- A Function which has fully qualified parameter types
- A tuple (a tuple of
String * String
in this case)
Another place you may see a Let
binding is in a class, but we will cover this in more detail in a later article in this series.
You can read more about the Let
binding using MSDN.
Use
The Use
binding is quite similar to the Let
binding in that it is binding a value to the result of an expression. The key difference is that the Use
binding is designed to work with IDisposable
types and will automatically Dispose
of the value when it is no longer in scope. This is quite similar to the .NET using
keyword, though I do not believe that the F# Use
binding will be exactly the same as the .NET using
keyword semantics, as the .NET using
keyword is really a try
/ finally
with a Dispose()
call in the finally
.
We have already seen an example of the Use
binding in the last post we did on Formatting Text, but just to remind ourselves, let's have a look at that again:
use sw = new StreamWriter(@"c:\temp\fprintfFile.txt")
fprintf sw "This is a string line %s\r\n" "cat"
fprintf sw "This is a int line %i" 10
sw.Close()
In this example, the Use
binding ensures that the StreamWriter
will have its Dispose()
method called after the sw.Close()
call seen above.
A Couple of Points
Use Only Works with IDisposables
, and you will get a compilation error, should you try and use it with anything else, as shown below:
As the Dispose()
method is called at the end of a Use
binding, care should be taken to not return a value that was bound using a Let
.
i.e : Don’t do this:
let Write =
use sw = new StreamWriter(@"c:\temp\fprintfFile.txt")
sw
If you absolutely need to pass an IDisposable
around that is part of a Use
binding, you can use a callback instead. So something like this would work, but I would stop and ask you have you got your design right if you are doing this sort of thing?
let Write callback =
use sw = new StreamWriter(@"c:\temp\fprintfFile.txt")
fprintf sw "Write is writing to the StreamWriter"
callback sw
sw
let callback sw = fprintf sw "sw is the StreamWriter"
let disp = Write callback
Do
Use a do
binding to execute code without defining a function or value. A Do
binding MUST always return Unit (no value / void essentially). In a lot of cases, you will be able to omit the Do
binding value and things will work just as expected.
Here are some examples of using the Do
binding:
do printf "doing the do"
do printf "print a sum %i" 1 + 1
do 1 + 1
If I instead show you a screen shot of the code above, you will see that the compiler complains if you try and use Do
with a Non Unit result.
You have 2 choices as the error message states, which is to either:
- Use the forward pipe operator to pipe the results to ignore
- Create a
Let
binding
I have shown an example of each of these below:
let x = 1 + 1
do printf "print a sum %i" x
do (1+1 |> ignore)
You can read more about the Let
binding using MSDN.
Let! Use! and Do!
Although I don’t want to discuss these just yet, you may on occasion see Let! / Use! / Do!, and when you do, these are part of what is known as a computation expression. The place you are most likely to see these is within F#s Asynchronous Workflows which we will be going through in one of the final articles. If I am man enough, I may even attempt to explain how you can create your own “Computation Expression” though they are quite an abstract concept and are quite an advanced topic, so now is not the time for them.