This
walkthrough demonstrates many of the features of the F# integration into Visual
Studio 2010.
F#
is a new programming language for the .NET platform which combines functional,
object-oriented and explorative programming.
This walkthrough focuses on the integration of F# into Visual Studio
2010. For a walkthrough of the F#
language itself, see the Visual Studio 2010 and .NET Framework 4.0 Training Kit
or http://msdn.microsoft.com/fsharp.
F#
is a general-purpose programming language, and along with it’s tools, can be
used to develop a wide array of applications, components, tools and
scripts. This walkthrough takes one path
through the use of the F# language, libraries, editor, project system and
debugger, highlighting the features of each.
Walkthrough
The
scenario covered in this walkthrough is based on a developer at a bank who
wants to do historical analysis of stock price data. This developer begins their work with an
informal, ad-hoc analysis of the data, and as they develop a better understanding
of the data, and a collection of functionality for working with the data, they
integrate the results into a component of a full-blown analysis application
developed by a colleague.
Scripting with F#
1.
Create a new F# script. Go to File | New
| File, then select Script and F# Script.
Save as “stockanalysis.fsx”.
2. Use
.NET APIs from F# to pull in data from the Web. Type in the code below.
- Notice: Colorization
of strings and keywords
- Notice: Completion
lists appear after every “.” is typed
- Notice: Invoking
“Display Word Completion” (ctrl-space, ctrl-j, ctrl-k,w) in the middle of an
identifier brings up a completion list
- Notice: Hovering
over any identifier in the code provides QuickInfo
open System.Net
open System.IO
let url = "http://ichart.finance.yahoo.com/table.csv?s=MSFT&d=10&e=10&f=2008&g=d&a=2&b=13&c=1986&ignore=.csv"
let req = WebRequest.Create(url)
let resp = req.GetResponse()
let stream = resp.GetResponseStream()
let reader = new StreamReader(stream)
let csv = reader.ReadToEnd()
3. Execute
code with F# Interactive. Select all the
code (ctrl-A) and right-click, then select ‘Send to F# Interactive’
(alternatively, alt-enter).
- Notice:
If not visible already, the F# Interactive toolwindow appears
- Notice:
Code executes live within the F# Interactive
- Notice:
F# Interactive responds with:
val url : string =
"http://ichart.finance.yahoo.com/table.csv?s=MSFT&d=10&e=10&f="+[36
chars]
val req : WebRequest
val resp : WebResponse
val stream : Stream
val reader : StreamReader
val csv : string =
"Date,Open,High,Low,Close,Volume,Adj Close
2008-11-10,21.85,21"+[287779 chars]
>
4. Inspect
data with the F# Interactive. At the F#
Interactive prompt, type “csv;;” then enter.
Also type “csv.Length;;” then enter.
- Notice:
The F# interactive prints the value of the string ‘csv’, and the length.
1986-03-18,29.50,29.75,28.50,28.75,67766400,0.08
1986-03-17,29.00,29.75,29.00,29.50,133171200,0.09
1986-03-14,28.00,29.50,28.00,29.00,308160000,0.08
1986-03-13,25.50,29.25,25.50,28.00,1031788800,0.08
"
> csv.Length;;
val it : int = 287846
>
5. Write
F# code to parse CSV data. In the
editor, add the code below. As each line
is added, select the code added in this section so far, and hit alt-enter to see
the partial results.
- Notice: Continue to get colorization, quick info and
correct completion lists on “.” in the middle of complex nested expressions.
- Notice: While code is incomplete (or incorrect), live
syntactic and semantic error squiggles appear in the code.
- Notice:
Pipelines (“|>”) and F# Interactive allow easy partial execution of data
processing code.
let prices =
csv.Split([|'\n'|])
|>
Seq.skip 1
|>
Seq.map (fun line -> line.Split([|','|]))
|>
Seq.filter (fun values -> values.Length = 7)
|>
Seq.map ( fun values ->
System.DateTime.Parse(values.[0]),
float
values.[6])
6. Give
this functionality a name. Remove MSFT
from definition of ‘url’, and replace with ”+ticker+”. Add let
ticker = “MSFT” on
the line above. Select all code except
this one new line, and hit <tab>.
Above the indented block of code, add let
loadPrices ticker =. At the end of the indented block, add prices.
- Notice:
Whitespace (indentation) is significant in F# - indicates nesting level.
- Notice:
Lightweight code abstraction provided by the language syntax – means
<tab> is almost like an ‘Extract Method’ refactoring
- Notice: Code now shows the following:
open System.Net
open System.IO
let ticker = "MSFT"
let loadPrices ticker =
let url =
"http://ichart.finance.yahoo.com/table.csv?s="+ticker+"&d=10&e=10&f=2008&g=d&a=2&b=13&c=1986&ignore=.csv"
let req =
WebRequest.Create(url)
let resp =
req.GetResponse()
let stream =
resp.GetResponseStream()
let reader =
new StreamReader(stream)
let csv =
reader.ReadToEnd()
let prices =
csv.Split([|'\n'|])
|>
Seq.skip 1
|>
Seq.map (fun line -> line.Split([|','|]))
|>
Seq.filter (fun values -> values.Length = 7)
|>
Seq.map ( fun values ->
System.DateTime.Parse(values.[0]),
float values.[6])
prices
7. Use
this functionality on new inputs. Select
all the code and hit alt-enter to execute with F# Interactive. At the F# Interactive prompt, call the new ‘loadPrices’
function on other ticker symbols: ‘GOOG’, ‘AAPL’, ‘ORCL’, ’EBAY’.
- Notice: Old definitions are not lost in F#
Interactive, but new definitions are available as well.
- Notice: Non-trivial structured data is rendered by
the pretty printer.
> loadPrices
"EBAY";;
val it :
seq<System.DateTime * float>
= seq
[(11/10/2008 12:00:00 AM {Date = ...;
<...snip...>
TimeOfDay =
00:00:00;
Year = 2008;},
14.72); ...]
>
Component Development with F#
1. Create
a Library project to expose this functionality.
File | New Project… | Visual F# | Library Project to create a new
Library project named “StockAnalysis”.
Copy/paste code from stokanalysis.fsx into Module1.fs. In Solution
Explorer, rename “Module1.fs” to “StockLoader.fs”.
- Notice:
Visual F# has project templates for creating .NET applications and libraries as
well as a tutorial
- Notice:
Visual F# is listed at top level in New Project Dialog if the F# profile was
selected, or under Other Languages if another profile was selected.
- Notice:
Default F# Library template provides a
code file (.fs) and a script (.fsx)
2. Author
a new F# class which exposes desired functionality. In Solution Explorer, right click on the
project node and select Add | New Item… then F# Source File. Name it “StockAnalysis.fs”. Select Script.fsx in the solution explorer
and right-click then Move Down (alternatively alt-down). Type the code below in this file.
- Notice:
F# projects are order-dependent – the functions, types and modules defined in a
file are available in all files after it in the project.
- Notice:
In Solution Explorer, a new file is added at the end, not in alphabetical
order.
- Notice:
In Solution Explorer, context menu commands and alt-up, alt-down keybindings
allow reordering files.
- Notice:
Can author Object Oriented .NET classes naturally in F#.
namespace StockAnalysis
open StockLoader
/// Provides analysis of historical stock data
type StockAnalyzer(lprices, days) =
let prices =
lprices
|>
Seq.map snd
|>
Seq.take days
///
Construct StockAnalyzer objects for each ticker over the
/// given
number of days
static
member GetAnalyzers(tickers, days) =
tickers
|>
Seq.map loadPrices
|>
Seq.map (fun lprices -> new StockAnalyzer(lprices, days))
member
sa.Return =
let
lastPrice = prices |> Seq.nth 0
let
startPrice = prices |> Seq.nth (days - 1)
lastPrice / startPrice - 1.0
member sa.MeanAndStdDev
=
let
logRets =
prices
|> Seq.pairwise
|> Seq.map (fun (x,y) -> log (x/y))
let mean
= logRets |> Seq.average
let sqr
x = x * x
let
variance = logRets |> Seq.averageBy (fun x -> sqr (x - mean))
(mean,
sqrt variance)
3. XML
Doc Comments. In Solution Explorer,
right click the project and select Properties.
Select the Build tab and then check the “XML Documentation” checkbox at
the bottom of the page:
- Notice:
Can generate XML documentation for any F# assembly
- Notice:
XML Documentation is generated into the output path by default
.jpg)
4. Build. Hit ctrl-shift-b or F6 to build.
- Notice:
Output directory contains .dll, .pdb and .xml files
- Notice:
Output window shows the following:
------ Build started:
Project: StockAnalysis, Configuration: Debug Any CPU ------
D:\Program Files\Microsoft F#\v4.0\fsc.exe
-o:obj\Debug\StockAnalysis.dll -g --debug:full --noframework --define:DEBUG
--define:TRACE --optimize- --tailcalls- -r:"D:\Program Files\Microsoft
F#\v4.0\FSharp.Core.dll" -r:"D:\Program Files\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\mscorlib.dll"
-r:"D:\Program Files\Reference
Assemblies\Microsoft\Framework\.NETFramework\v4.0\System.Core.dll"
-r:"D:\Program Files\Reference
Assemblies\Microsoft\Framework\.NETFramework\v4.0\System.dll" --target:library
--warn:3 --warnaserror:76 --vserrors --utf8output --fullpaths --flaterrors
StockLoader.fs StockAnalysis.fs
StockAnalysis ->
D:\Users\lukehDDNET\Documents\Visual Studio
10\Projects\StockAnalysis\StockAnalysis\bin\Debug\StockAnalysis.dll
========== Build: 1
succeeded or up-to-date, 0 failed, 0 skipped ==========
5. Add
a C# client application. Right click the
solution and select Add | New Project… then C# then Visual C# | Console
Application. Name it “CSharpDriver”. Right click its References node and select
Add Reference… then in Projects tab, select StockAnalysis. Right click the project node and select Set
as Startup Project. Type the code below
in the body of the Main method.
- Notice:
Can add project-to-project references to/from C#/F#
- Notice:
F# defined namespaces and types can be used from C# like any other type
- Notice:
F# doc-comments are available in C# intellisense
- Notice:
Tuples returned from F# are .NET4.0 Tuple instances, which can be used natively
from C#, without referencing the F# runtime assembly FSharp.Core.dll.
var tickers = new[] { "MSFT",
"GOOG", "ORCL", "EBAY" };
var analyzers =
StockAnalysis.StockAnalyzer.GetAnalyzers(tickers, 365);
foreach (var item in analyzers)
{
Console.WriteLine("Return = {0}, \t Mean = {1}, \t StdDev =
{2}", item.Return, item.MeanAndStdDev.Item1, item.MeanAndStdDev.Item2);
}
6. Debug. Hit F11 to build, launch under debugger, and
step into. Hit F11 a few more times
until stepping into F# code in the body of the GetAnalyzers member.
- Notice:
Debugging from C# into F# works seamlessly.
- Notice:
Debugging spans are “expression-based”.
- Notice:
Locals window shows the values of parameters “tickers” and “days”
- Notice:
Continuing to step walks through the evaluation of the rest of the application
- Notice:
Debugger commands like “Run to cursor”, “Set next statement”, “Insert
Breakpoint”, “Add Watch” and “Go to Disassembly” all work as expected.
.jpg)
Asynchronous Programming with F#
1. F#
PowerPack. Go to http://msdn.microsoft.com/fsharp and click the link
to download the F# PowerPack. In the
StockAnalysis project, righ click Add Reference… then browse to the
FSharp.PowerPack.dll assembly on disk and select it.
- Notice: The F# powerpack is a set of handy extensions
to F#
- Notice:
The F# PowerPack can version independently of the core F# release
- Notice:
The F# PowerPack contains a few assemblies and tools – in this walkthrough
we’ll use some convenient asynchronous programming helpers that are available
in the PowerPack.
- Notice: the F# PowerPack is set to Copy Local when
refrenced
[Note: If the F# PowerPack is not yet available when
using the Walkthrough, add the following definitions directly to the beginning
of StockLoader.fs instead of the directions above.
type System.Net.WebRequest with
member
req.AsyncGetResponse() =
Async.BuildPrimitive(req.BeginGetResponse, req.EndGetResponse)
type System.IO.StreamReader with
member
reader.AsyncReadToEnd() = async {
do!
Async.SwitchToNewThread ()
let res
= reader.ReadToEnd()
do!
Async.SwitchToThreadPool ()
return
res }
EndNote]
2. Asynchronous
Workflows. In StockLoader.fs, make the
changes on the four lines indicated below.
- Notice:
Asynchronous code maintains the same programming style as synchronous code.
- Notice:
“async” keyword introduces an asynchronous block of code, with “let!”
indicating an asynchronous action inside the async code.
- Notice:
The result of adding these is that loadPrices now defines a computation which
can be run without blocking the thread it’s executed on.
open System.Net
open System.IO
let ticker = "MSFT"
let loadPrices ticker = async {
let url =
"http://ichart.finance.yahoo.com/table.csv?s="+ticker+"&d=10&e=10&f=2008&g=d&a=2&b=13&c=1986&ignore=.csv"
let req = WebRequest.Create(url)
let! resp = req.AsyncGetResponse()
let stream = resp.GetResponseStream()
let reader = new StreamReader(stream)
let! csv = reader.AsyncReadToEnd()
let prices =
csv.Split([|'\n'|])
|>
Seq.skip 1
|>
Seq.map (fun line -> line.Split([|','|]))
|>
Seq.filter (fun values -> values.Length = 7)
|>
Seq.map ( fun values ->
System.DateTime.Parse(values.[0]),
float values.[6])
return prices }
3. Consuming
Asynchronous Code. Open the
StockAnalysis.fs file. Check the error
list, and see that there is now an error in StockAnalysis.fs. Change the GetAnalyzers member as indicated
below.
- Notice:
Error list works across files opened in the editor, and can be used to guide
code refactoring
- Notice:
Background type checking hints at the appropriate fixes below (i.e. what is
needed to turn a seq<async<’a>> into a seq<’a>?)
- Notice:
Async.Parallel provides a way to inject parallelism using asynchronous
workflows
- Notice: The result of these changes is that all the
stock tickers will be downloaded and processed simultaneously instead of in
sequence. This leverages CPU parallelism
for the processing, but even more importantly, leverages I/O parallelism
available in making the web connections.
///
Construct StockAnalyzer objects for each ticker over the
/// given
number of days
static
member GetAnalyzers(tickers, days) =
tickers
|>
Seq.map loadPrices
|>
Async.Parallel
|>
Async.Run
|>
Seq.map (fun lprices -> new StockAnalyzer(lprices, days))
4. Parallel
Stacks Debugging Window. Set a breakpoint
on the line “let stream = …” in
StockLoader.fs. Hit F5 to run to the
breakpoint. In the main menu, select
Debug->Windows->Parallel Stacks to open the window shown below. Press F5 a few (3-4) times.
- Notice:
You can see how the asynchronous workflow is executing on multiple threads in
the Parallel Stacks window
- Notice:
Periodically, there will be two threads executing loadPrices, indicating CPU
parallelism
- Notice:
Almost always there will be a thread in a sleep, wait or join, often indicating
I/O parallelism.
.jpg)
Deploying the Application
1. Multitargeting:
In Solution Explorer, right click the F# StockAnalysis project and select
Properties. Select the Application tab
and change the Target Framework setting to “.NET Framework 4.0 Client Profile”.
- Notice:
F# allows targeting .NET4.0 and .NET4.0 Client Profile
- Notice:
The .NET4.0 Client Profile is a smaller version of the .NET Framework which
contains just the key functionality needed for .NET client applications.
- Notice:
Changing the target framework requires reloading the project
- Notice:
After changing the framework target to the .NET4.0 Client Profile, some
assembly references are no longer allowed in the Add Reference dialog, and are
grayed out.
.jpg)
2. Set
the PreRequisites: Double-click the Properties node in CSharpDriver and open
the Publish tab. Select the
“Prerequisites…” button and check the box for “Microsoft Visual F#
Redistributable Package”.
- Notice: F# has a separate redist, which should be
installed on client machines which will run application developed with F#.
- Notice: This redist can be explicitly added as a
prerequisite for a ClickOnce or Setup project.
.jpg)
3. Deploy
the C# application with ClickOnce. Right
click on the CSharpDriver project and select Publish… then Finish. Run the resulting CsharpDriver.exe.
- Notice:
F# Redist is included with the application
- Notice:
Running the application installs the F# redist and executes the app
successfully.
.jpg)