Export (0) Print
Expand All

F# in Visual Studio 2010

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:  Data is “live”
  • 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

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.

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.

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.

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.

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.

Show:
© 2014 Microsoft