TechEd 2008 notes: Busy .NET Developer’s Guide to F#
Monday, June 9th, 2008I’m a bit late getting my Thursday and Friday notes posted. Thursday night was Universal Studios, so I didn’t get any posting done then, and by Friday night I was just plain worn out. So here we go again, starting with Thursday morning.
Busy .NET Developer’s Guide to F#
Ted Neward (talking about the slides)
Neward & Associates
www.tedneward.com
Luke Hoban (doing the demos)
F# Program Manager
blogs.msdn.com/lukeh
F# is a functional, object-oriented, imperative and explorative programming language for .NET.
- OO you already know.
- Imperative = sequential execution. C#, batch files, etc.
- Explorative = REPL loop / interactive prompt. Executing code on the fly in Visual Studio.
- Functional
- This doesn’t mean “F# is functional” as opposed to “C++ is dysfunctional”.
- Mathematical definition of “functions”.
- f(x) = x + 2. So if you pass 5, you get 7, no matter how many times you do it.
- Immutable; no shared state.
- This means we can do some substitution. Can break down into pieces, solve for part of it, put it together and get the right answer.
- Look to create functions that can be composed into higher-order functions.
- Very concurrency-friendly.
F#: The combination counts!
- Strongly typed. Makes sure you didn’t do anything really, really stupid. Historically, compilers have been rather stupid about this: if I say
s = "Luke", why do I have to tell the compiler it’s a string? It already knows, after all. - Succinct
- Scalable
- Libraries
- Explorative
- Interoperable
- Efficient
What is F# for?
- General-purpose language
- Can be used for a broad range of programming tasks
- Some particularly important domains
- Financial modeling and analysis
- Data mining
- Scientific data analysis
- Domain-specific modeling
- Academic
- Speed and power
Install F# addon into Visual Studio
“F# Interactive” window (REPL prompt). This is a dockable window in the IDE. Just type code in. E.g.
1+1;;
Can also highlight code in the code editor and press Alt+Enter, which runs that code in the interactive window; then you don’t need ;; to tell it you’re done. (So what do you do if you use ReSharper and Alt+Enter already does lots of cool stuff? I wonder if keybindings in the editor are language-sensitive.)
// Turn on the significant whitespace option #light System.Console.WriteLine "Hello World" System.Windows.Forms.MessageBox.Show "Hello World" printfn "Hello World"
The Path to Mastering F#
- Covered today:
- Scoping and “let”
- Tuples
- Pattern matching
- Working with functions
- Sequences, lists, options
- Records and unions
- Basic imperative programming
- Basic objects and types
- Parallel and asynchronous. Comparing C# examples from MSDN to their F# equivalents.
- Not covered today:
- F# libraries
- Advanced functional/imperative
- Advanced functional/OO
- Meta-programming
- They can’t teach us a programming langugae in 75 minutes. They just want to make us dangerous. Get us to where we can read the examples.
“let” and scoping
Let: binds values to identifiers
let data = 12
let f x =
let sum = x + 1
let g y = sum + y*y
g x
- No semicolons. Language can figure out where the statement ends. Also makes use of significant whitespace.
- No parentheses.
- Type inference. The static typing of C# with the succinctness of a scripting language. We can specify types when we want to be unambiguous. There’s also some generic stuff that can happen.
- No real distinction between variables and function declarations.
- Nested functions.
sumandgaren’t visible outside the scope off. So we have additional encapsulation that C# and VB don’t have. - Closures.
- Last value calculated becomes the return value.
- Immutability (by default).
let PI = 3.141592654 PI <- 4.0
That’s an error: “This value is not mutable.”
- Is this a property or a field? We don’t care.
<-is assignment.- You can make something mutable. But they do the right thing, by default, with respect to concurrency.
open System
open System.IO
open System.Net
let req = WebRequest.Create("http://www.live.com")
let stream = req.GetResponse().GetResponseStream()
let reader = new StreamReader(stream)
let html = reader.ReadToEnd()
html
let http(url: string) =
let req = WebRequest.Create(url)
use resp = req.GetResponse()
use stream = resp.GetResponseStream()
use reader = new StreamReader(stream)
let html = reader.ReadToEnd()
html
- Actually did provide type annotation in the above example. Sometimes it can’t infer the type. This is one such case, because
WebRequest.Createis overloaded. These are fairly rare in the bulk of F# code. - I’m curious what happens if you don’t annotate this example; that’s a detail they didn’t show. Compiler error?
useis like C#’susingblock.- I’m curious how it knows how big the scope is, i.e., where in the code to insert the call to
Dispose.
let html2 = ""
html2 <- http("http://www.live.com")
- Red squiggle under the second
html2. Background compilation. This is why you can hover over an identifier and see its type. - If you really want to make it mutable:
let mutable html2 = ""
html2 <- http("http://www.live.com")
Lists, Options and Patterns
- Lists are first-class citizens
- Options provide a some-or-nothing capability
- Pattern matching provides particular power
let list1 = ["Ted"; "Luke"]
let option1 = Some("Ted")
let option2 = None
match Option1 with
| Some(x) -> printfn "We got an %A" x
| None -> printfn "Nope, got nobody"
- Semicolons as delimiters inside lists. Yes, it’s silly that it’s not comma. Red squigglies will help you figure out when you get it wrong.
- Option type: you can either have some of a particular value, or none. (This looks pretty similar in intent to nullable types in C#.)
- Looks like
SomeandNoneare predefined as part of the language or the library, though they didn’t explicitly go into this. Someis a generic; it can contain any type. Generics are very lightweight, almost invisible.- Pattern matching. Note that it not only matches Some(x), it also extracts the value (
x) for us to use.
Functions
- Like delegates + unified and simple
(fun x -> x + 1) let f x = x + 1 let g = f val g : int -> int
- First: Anonymous function.
- Second: same thing, but we’re giving it a name.
- Third: creating another identifier that’s bound to the same value as f.
- We’re starting to treat functions as values.
- This lets us do interesting things like higher-order functions, which are very difficult to express in a language like C#.
- Nobody’s saying you can’t do this in C#. But F# syntax is more succinct, and sometimes a little bit more powerful.
- Nobody’s saying you need to stop using C#. If you want to continue creating tomorrow’s legacy code, nobody’s going to stop you.
- I think the fourth one isn’t actually code, but rather the thing F# displays when it’s telling you the type. I didn’t quite catch that bit, though, so I’m not sure.
Lists
let sites = ["http://live.com"; "http://yahoo.com"] let sites' = "http://live.com"::"http://yahoo.com"::[] let sites'' = ["http://live.com"] @ ["http://yahoo.com"]
::means “prepend this element to this list”@appends two lists.- Names can have prime and prime-prime suffixes.
Arrays
let sitesArray = [|"http://live.com"; "http://yahoo.com"|]
- Arrays can be useful. They’re still immutable, but can be very performant.
- They did not go into any details on how arrays are different from lists.
Tuples
let nums = (1,2,3) let info = (1,true,"hello")
- Packages up some data. Type is reported as
int * int * int.
Computed lists
[ 0 .. 10 ] [ for i in 0 .. 10 -> (i, i*i) ] [ for i in 0 .. 20 -> (i, sin(float(i)/20.0*Math.PI)) ]
- First: just returns a list
- Second and third: list of tuples
Example: Web crawling
open System.Text.RegularExpressions
let linkPath = "href=\s*\"[^\"h]*(http://[^&\"]*)\""
let msft = http("http://www.microsoft.com")
let msftLinks = Regex.Matches(msft, linkPat)
[ for m in msftLinks -> m.Groups.[1].Value ]
let getLinks (txt:string) =
[ for m in Regex.Matches(txt,linkPat) -> m.Groups.[1].Value ]
let getFirstLink html =
let links = getLinks html
match links with
| [] -> None
| firstLink :: _ -> Some firstLink
Unions and pattern matching
type Expression =
| Num of int
| Neg of Expression
| Add of Expression * Expression
let expr = Add(Add(Num 1, Neg(Num 2)), Num 3)
- Unions are like an enum, but one that can carry data along with it. Very succinct and clear.
let rec evaluate expr =
match expr with
| Num(n) -> n
| Neg(e') -> -(evaluate e')
| Add(e', e'') -> (evaluate e') + (evaluate e'')
evaluate expr
let recmeans it’s a recursive function. You have to make that explicit.
Functions and Composition
let htmlForSites = List.map http sites
- First argument is a function; second argument is the list. Returns a new list with the function results.
- If you look at the type of List.map, it’s
val map : ('a -> 'b) -> 'a list -> 'b list. The prepended'mean it’s a generic.
sites |> List.map http |> List.map (fun x -> x.Length) |> List.fold_left (+) 0
- |> is the piping operator.
- List.fold_left is used to sum all the lengths.
- Three different kinds of functions:
- Defined explicitly, with a name: http
- Anonymous function: (fun x…
- Operator, passed around and used as data: (+)
GUI and databinding
open System.Drawing
open System.Windows.Forms
let form = new Form(Visible = true,
Text = "A simple F# form",
TopMost = true,
Size = Size(500, 400))
let data = new DataGridView(Dock = DockStyle.Fill)
form.Controls.Add(data)
data.DataSource <- [| ("http://www.live.com", 1) |]
// more stuff to set column headers
let limit = 50
let collectLinks url = getlinks (try http(url) with _ -> “”)
- An array of tuples can be bound to a datagrid. How cool is that?
trykeyword is used to say “ignore anything that throws an exception”. If F# has anything liketry..finallyortry..catch, they didn’t demo it today.
let rec crawl visited url =
data.DataSource <- List.to_array visited
Application.DoEvents()
if (visited |> List.exists (fun (site,_) -> site = url))
or visited.Length >= limit
then
visited
else
let links = collectLinks url
let visited' = (url, links.Length) :: visited
List.fold_left crawl visited' links
crawl [] "http://live.com"
Object Oriented + Functional
#light namespace Library type Vector2D(dx:double, dy:double) = member v.DX = dx member v.DY = dy member v.Length = sqrt(dx*dx+dy*dy) member v.Scale(k) = Vector2D(dx*k, dy*k)
- F# doesn’t differentiate between properties and methods. They want you thinking about the problem, not about software engineering.
- Constructor parameters are in the type declaration itself. That’s the primary constructor that all others defer to, ’cause that’s usually what you do. No wasted syntax initializing the fields; it’s constructor signature, field declarations, and assignments all in one.
- When you build types in F#, you can see them in C#. X, Y, and Length are properties; Scale is a function.
- F# doesn’t automatically realize that fields are immutable and it can precompute the length. F# is immutable by default, but it’s hard to figure out how aggressive to be. (That doesn’t make a whole lot of sense based on what I know so far — you have to explicitly say when you want something to be mutable — but I guess there could be more going on somewhere.) If calculating Length is expensive, you have to precompute it yourself, thusly:
type Vector2D(x : float, y : float) =
let norm = sqrt(x*x + y*y)
member this.Length = norm
- That
letbecomes constructor code.
override this.ToString() = "hello"
- Intellisense works — type
override this.and a completion list pops up.
Case Study
- The Microsoft adCenter Problem
- Cash-cow of Search
- Selling “web space” at www.live.com and www.msn.com.
- What’s the chance, if I put up a certain ad, that a user will click on it?
- Internal competition for a better clickthrough-prediction algorithm
- Winning team used F#
Their observations
- Quick coding. Less typing, more thinking.
- Agile coding
- Scripting. Interactive “hands-on” exploration of algorithms and data over smaller data sets.
- Performance. Immediate scaling to massive data sets.
- Memory-faithful
- Succinct. Live in the domain, not the language. They were able to have non-programmers helping with the code.
- Symbolic
- .NET integration
Taming asynchronous I/O
- Pages of C# code
let ProcessImageAsync(i) =
async { let inStream = File.OpenRead(sprintf "source%d.jpg" i)
let! pixels = inStream.ReadAsync(numPixels)
let pixels' = TransformImage(pixels, i)
let outStream = File.OpenWrite(...
do! outStream.WriteAsync(pixels')
do Console.WriteLine "done!" }
let ProcessImagesAsync() =
Async.Run (Async.Parallel
[ for i in 1 .. numImages -> ProcessImageAsync(i) ])
- let! means “do this asynchronously”
- Async.Parallel means “do this stuff in parallel, and just make it work”
F# Roadmap
- May 2008: MS Research F# refresh release
- Language and libraries clean-up and completion
- Fall 2008: F# CTP release
- Language decisions finalized
- Foundation of full integration into Visual Studio
- Future: releases with betas and RTM of next version of Visual Studio
- Either as an add-in or in-the box
Resources
- research.microsoft.com/fsharp
- blogs.msdn.com/dsyme
- Community
- F# training (Pluralsight, www.pluralsight.com)
- Samples in download
- Books
- Expert F#