Introduction to debugging in F#
There is one aspect of understanding running F# code that is crucial: debugging F# code. We have to be able to debug F# code, especially when we have very large projects that have hundreds of F# code files, not to mention when each of the code files may have too many lines of code. For example, having to check a running F# code that has more than 2,000 lines.
The following are the advantages of the debug features:
- Isolating the error and focusing on it by inserting a breakpoint can ease the fixing of an error or bug. Developers are gaining more productivity because they can fix errors/bugs faster.
- Debugging can also provide insightful information about the correctness of any value returning from a function.
- Debugging can also be used to trace bugs further by examining the results from other referenced libraries as well. It is possible that we may use the referenced library incorrectly or the referenced library may also have bugs.
Visual F# in Visual Studio 2015 also has debugging capabilities. It was not as powerful when it was introduced in Visual Studio 2008 as additional add-on, but now the debugging experience is much better. It has been integrated with the Visual Studio extensibility model nicely, providing, for example, faster execution while running in the debug mode and having conditional breakpoints.
It is different from the C#/VB debugger because F#, although being a strongly and strictly typed language, currently has no support for evaluating expressions in the debugger's immediate windows in Visual Studio 2015.
Note
Some experienced F# developers may argue that this additional debug feature is not a big concern at all as F# has a tendency to enforce type restriction and correctness at the fabric of F# as a programming language. But for most other developers, especially those who jump from C#/VB to F#, the overall debugging experience is still lacking some features.
Currently these are the differences between the F# and C#/VB debugger in Visual Studio 2015:
Feature |
F# |
C#/VB |
Breakpoint insertion |
Yes. |
Yes |
Condition in breakpoint |
Yes. |
Yes |
Intellisense in editing condition in breakpoint |
Not supported because Intellisense is not yet linked to the whole infrastructure of the Visual Studio 2015 IDE extensibility and the F# compiler. There is a plan to have this feature for the next Visual Studio release after Visual Studio 2015. |
Yes |
Lightbulb assistant |
Not available. There is a plan to have this feature for the next Visual Studio release after Visual Studio 2015, but the exact planned release is not quite clear. |
Yes |
Expression evaluation in immediate window |
Not available. |
Yes |
Locals value |
Yes. |
Yes |
Auto watch value |
Yes. |
Yes |
Other than the features in the previous table, basic debugging with breakpoints in Visual F# is essentially the same as debugging in C#/VB.
Let's take some code to debug. To quickly have some code, we can use the F# 3.0 sample from CodePlex at:
http://fsharp3sample.codeplex.com/
After downloading the ZIP file of the code samples, unzip it to a folder and open the SampleProject.sln
solution file in Visual Studio.
Note
You may read Readme.txt
first before using the whole sample code. This readme guide is available in the Solution Item
folder when opened in Solution Explorer.
Now, your screen should look like this:
Some of the samples in F# 3.0 are not valid anymore. You have to register for Azure DataMarket to access the F# Type Provider of Azure DataMarket.
There are some compiler errors if we try to rebuild the solution without changing the code at all, and one of the sample type providers, ESRI DataMarket, is not working.
Based on those invalid type provider conditions, to build this sample solution successfully, you have to follow these steps:
- Register with Azure DataMarket. You need to have your own Azure account key to access Azure DataMarket.
- The ESRI sample has not been working since 2014. Please comment the lines from line 135 to line 157 in the
Samples.TypeProviders.AzureMarketPlace.fs
file. - Rebuild the solution. This will compile the whole solution and also resolve the type provider in the background for us to use.
- Now open the
Samples.Beginners.fs
file. Put the debugger breakpoints at lines 19 and 20 by clicking the line. - To add breakpoints, you can simply toggle the highlighted column on the left before the line number like this:
- And we can also add breakpoints by right clicking and choosing Breakpoints.. and then Insert Breakpoint.
- Compile the code by initiating the Build Solution. Then press F5 to run. The window of F# Micro Sample Explore is displayed:
This sample is actually a showcase of many F# features, from basic language constructs, units of measure, type providers, and LINQ, to concurrency such as async and parallelism.
- Now expand the Basic node on the left and choose Basic Data Types, and then choose the last node of Integer Arithmetic, as illustrated here:
- Going back to the source code of
Samples.Beginner.fs
, we can see that the node name is also the same as the name of the attributes in the code to categorize:[<Category("Basic Data Types"); Title("Integer Arithmetic"); Description("This sample shows some basic integer arithmetic")>] let SampleArithmetic1() = let x = 10 + 12 - 3 let y = x * 2 + 1 let r1,r2 = x/3, x%3 printfn "x = %d, y = %d, r1 = %d, r2 = %d" x y r1 r2
- Click the Run Sample! button and Visual Studio will stop the execution at the breakpoint:
Now we can debug our code easily. We can also look at the value of the variables or symbols that are currently in scope by checking the value at the Locals window.
- Press F10 to step over, and now we see the evaluated value of
x
andy
:
Any local variables in Locals and watch expressions displayed in the Watch1 window always have the name, value, and type of the variables. The type displayed is using the F# keyword, not the full type name.
For example, int
is displayed instead of System.Int32
as shown in Locals.
We can always check other values as well if we have another global
or static global
variable in the Watch1 window. The values can contain immediate values from an expression, for example DateTime.Now
.
Unfortunately, we have to write using the full namespace of System.DateTime
, so we have to write the expression as System.DateTime.Now
:
This requirement to have the full namespace proves that debugger support in Visual F# still requires improvements compared to its C#/VB counterparts. Typing the full object name may be error prone, as F# watch does not support Intellisense yet.
After we have finished debugging and bug fixing, it is recommended to change the compilation to the Release mode. The Release mode will have a smaller compiled F# code and it executes faster because it does not contain debug symbols and any other debug information attached to the compiled code.
To change back and forth between Debug and Release is quite easy. We can simply change the mode at the drop-down menu in the Visual Studio toolbar:
There is no apparent distinction on the compiled DLL or EXE filename, other than the smaller size of the release mode.
To summarize, the following are the differences of the Debug mode and the Release mode:
Elements |
Debug |
Release |
Debug symbol (PDB) |
Included |
Not included. |
Size of compiled code |
Bigger than the release mode, excluding the PDB file |
Smaller than the debug mode. |
Code optimization |
Not optimized, as it is focused for debugging and it is also synchronized |
Yes, but the code will not be able to be debugged easily, as the code is optimized for executions.
In .NET 4.6 and Windows 10, it is optimized further by compiling into native code using the Ahead Of Time (AOT) model instead of Just In Time (JIT).
|
Compilation symbol availability |
|
Not applicable. |
Execution |
Slower than Release, as there is no optimization |
Fast, since it is optimized for runtime, and there is no debug symbol overhead. |
For more information about AOT and JIT, consult the MSDN Library at https://msdn.microsoft.com/en-us/library/dn807190(v=vs.110).aspx.