F# Primer
Use Functional Programming Techniques in the .NET Framework
Ted Neward
This article discusses:
|
This article uses the following technologies: .NET Framework, F# |
Contents
Why Use F#?
Installing F#
Hello, F#
The Let Expression
The For Keyword
The Pipeline
F# Can Do Objects, Too
Asynchronous F#
Fitting in with F#
A recent entry into the Microsoft® .NET Framework family, F# provides type safety, performance, and the ability to work like a scripting language, all as part of the .NET environment. This functional language was created by Don Syme of Microsoft Research as a syntax-compatible OCaml variant for the CLR, but F# has been moving quickly from the lab into the workshop.
As concepts of functional programming creep into more mainstream languages like C# and Visual Basic® through technologies like .NET generics and LINQ, the visibility of F# has grown within the .NET community—so much so that in November 2007 Microsoft announced that it would move F# into the stable of supported .NET programming languages.
For years the area of functional languages (ML, Haskell, and so on) has been considered more appropriate for academic research than for professional development. It isn't that these languages aren't interesting. In fact, some important enhancements to .NET—generics, LINQ, PLINQ, and Futures, for example—come from the application of functional programming concepts to languages that had never seen them before. The lack of interest in these languages has been based more on the fact that they targeted platforms of little relevance to developers writing programs for Windows®, they didn't integrate well with the underlying platform, or they didn't support key functionality such as relational database access, XML parsing, and out-of-process communication mechanisms.
However, the CLR and its "many languages, one platform" approach made it inevitable that more of these languages would make their way into the world of Windows development. It was equally inevitable that they would start to make their presence felt to practicing programmers. F# is one such language. In this article, I will introduce you to some of the concepts behind and advantages of F#. Then, to help you get started with F#, I will walk you through installation and writing several simple programs.
Why Use F#?
It will be obvious to a small percentage of .NET programmers that learning a functional language for the .NET Framework is a positive step forward in writing powerful software. For the rest, the motivation to learn F# is a complete mystery. How do developers benefit from F#?
Writing safe concurrent programs has become a principal concern in the past three years, as multicore CPUs have become more widespread. Functional languages help developers support concurrency by encouraging immutable data structures that can be passed between threads and machines without having to worry about thread safety or atomic access. Functional languages also tend to make it easier to write better concurrency-friendly libraries such as the F# asynchronous workflows, which I will examine later in this article.
Although it won't seem as such to programmers deeply steeped in object-oriented development, functional programs are often simpler to write and maintain for certain kinds of applications. Consider, for example, writing a program to convert an XML document into a different form of data. While it would certainly be possible to write a C# program that parsed through the XML document and applied a variety of if statements to determine what actions to take at different points in the document, an arguably superior approach is to write the transformation as an eXtensible Stylesheet Language Transformation (XSLT) program. Not surprisingly, XSLT has a large streak of functionalism inside of it, as does SQL.
F# strongly discourages the use of null values and encourages the use of immutable data structures. These together help reduce the frequency of bugs in programming by reducing the amount of special-case code required.
Programs written in F# also tend to be more succinct. You actually type less, in both senses of the term: fewer keystrokes and fewer places where the compiler must be told what the type of the variable, arguments, or return type must be. That can mean a lot less code to maintain.
F# has a similar performance profile to C#. However, it has a much better performance profile than comparable succinct languages, in particular the dynamic and scripting languages. And, like many of the dynamic languages, F# includes the tools that allow you to explore data by writing program fragments and executing them interactively.
Installing F#
Available as a free download at research.microsoft.com/fsharp/fsharp.aspx, F# installs not only all the command-line tools but also a Visual Studio® extension package that provides color syntax highlighting, project and file templates (including a very detailed example of F# code as a starter guide), and IntelliSense® support. There's also an F# Interactive shell, which can run inside of Visual Studio, allowing developers to take expressions from source file windows, paste them into the interactive shell window, and see the immediate results from the code snippet—within something like an enhanced Immediates window.
As I'm writing this, F# runs as an external tool inside of Visual Studio, meaning that some of the seamlessness that developers get with C# or Visual Basic is missing. F# also lacks ASP.NET page designer support, among other things. (This is not to say that F# can't be used in ASP.NET—far from it. It is simply that the Visual Studio support for F# doesn't provide the same kind of out-of-the-box drag and drop development experience for F# that it offers for C# and Visual Basic.)
Having said all that, however, the current release of F# can be used anywhere that any other .NET-compliant language can be used. In the next few pages you'll see some examples.
Hello, F#
The inevitable introduction to any language comes via the ubiquitous "Hello, World" program. F# shall be no different:
printf "Hello, world!"
While a tad anticlimactic, this tiny little sample shows that F# belongs to that category of languages that doesn't require an explicit entry point (C#, Visual Basic, and C++/CLI all do); the language assumes that the first line of the program is the entry point and executes from there.
To run this, the budding F# developer has two choices: compiled or interpreted. Running this inside the F# interpreter (fsi.exe) is trivial. Simply fire up fsi.exe from the command line and enter the above line into the resulting prompt, as shown in Figure 1.
Figure 1** Running 'Hello, World' from inside the F# Interpreter **(Click the image for a larger view)
Note that in the shell the statement has to be terminated with two semicolons. This is a quirk of the interactive mode and isn't required for compiled F# programs.
To run this sample as a standard.NET executable, fire up Visual Studio as usual and create a new F# project (which you'll find under Other Project Types). An F# project consists, at the beginning, of a single F# source file, called file1.fs. Opening this file reveals a large collection of sample F# code. Have a look at its contents, just to get an idea of what F# syntax looks like. When finished, replace the entirety of the file with the "Hello, world!" code shown earlier. Run the app and, not surprisingly, "Hello, world!" appears in a console application window.
If you prefer a command line, you can compile the code using the fsc.exe tool found in the \bin subdirectory of the F# installation directory. Note that fsc.exe acts like most command-line compilers, taking the source on the command line and producing an executable as a result. Most of the command-line switches are documented, though many should already be familiar if you have experience with the csc.exe or cl.exe compilers. Note, however, that one area where F# currently lags is with MSBuild; there is no direct support within the current install (1.9.3.7 as I write this) for MSBuild-driven compilation.
If you prefer your "Hello, world!" to be a bit more graphical, F# easily provides complete fidelity and interoperability with the underlying CLR platform, including the Windows Forms libraries. Try it out with this:
System.Windows.Forms.MessageBox.Show "Hello World"
Having the ability to make use of the .NET Framework Class Library as well as F# libraries makes the F# language attractive both to the math and scientific community that is already using functional languages like OCaml or Haskell and to the existing body of .NET developers worldwide.
The Let Expression
Let's take a look at some F# code that isn't as trivial as the traditional "Hello, world!". Consider the following:
let results = [ for i in 0 .. 100 -> (i, i*i) ]
printfn "results = %A" results
One curious element in this F# syntax is the let expression. This is the most important expression in the entirety of the language. Formally, let assigns a value to an identifier. The temptation for the Visual Basic and C# developer will be to translate that as "it defines a variable." This would be an untrue assumption. Rather, identifiers in F# embody two principles. First, an identifier, once defined, may never change. (This is how F# helps programmers create concurrency-safe programs because it discourages mutable state.) Second, the identifier can be not only a primitive or object type, as seen in C# and Visual Basic, but also a function type, similar to what you might see in LINQ.
Notice as well how identifiers are never explicitly defined as having a type. The results identifier, for example, is never defined; it is inferred from the right-hand side of the expression that follows it. This is known as type inference, and it represents the compiler's ability to analyze the code, determine the return value, and automatically plug that in. (This is similar to the new C# inferred type expressions via the var keyword.)
The let expression doesn't need to work solely with data. You can use it to define functions, which F# recognizes as first-class concepts. So, for example, the following defines an add function that takes two parameters, a and b:
let add a b =
a + b
The implementation does pretty much what you would expect it to: add a and b and implicitly return the result to the caller. This means that technically every function in F# returns a value, even if that value is not a value, known as the special name unit. This will have some interesting implications in F# code, particularly where it intersects with the .NET Framework class library, but for now, C# and Visual Basic developers can think of unit as roughly equal to void.
There are times when a function should ignore a parameter passed to it. To do so in F#, simply use the underscore as a placeholder for the parameter itself:
let return10 _ =
add 5 5
// 12 is effectively ignored, and ten is set to the resulting
// value of add 5 5
let ten = return10 12
printf "ten = %d\n" ten
Like many functional languages, F# permits currying, in which a function's application can be only partly defined, relying on its invocation to supply the remainder of the parameters:
let add5 a =
add a 5
In some ways, this is similar to creating an overloaded method that takes a different parameter set and calls into another method:
public class Adders {
public static int add(int a, int b) { return a + b; }
public static int add5(int a) { return add(a, 5); }
}
But there is a subtle difference. Notice that, in the F# version, no types are explicitly defined. This means that the compiler will do its type-inferencing magic, determine whether the parameter to add5 is type-compatible with being added to the integer literal 5, and either compile as such or flag an error. In fact, much of the F# language is implicitly type-parameterized (that is, makes use of generics).
In Visual Studio, hovering the pointer over the previously shown definition of ten will reveal that its type is declared as:
val ten : ('a -> int)
In F# this means ten is a value, a function that takes one parameter of any type, and yields an int result. The tick syntax is the rough equivalent to the <T> syntax in C#, so the closest translation to a C# function would be to say that ten looks like a delegate instance to a type-parameterized method whose type you'd really like to ignore (but can't under the rules of C#):
delegate int Transformer<T>(T ignored);
public class App
{
public static int return10(object ignored) { return 5 + 5; }
static void Main()
{
Transformer<object> ten = return10;
System.Console.WriteLine("ten = {0}", return10(0));
}
}
The For Keyword
Now let's look at the for keyword in the first example:
#light
let results = [ for i in 0 .. 100 -> (i, i*i) ]
printfn "results = %A" results
Starting from the top of the code, notice the #light syntax. This is a concession to non-OCaml programmers coming to F#, relaxing some of the syntax requirements of the OCaml language, and using significant white space to define blocks of code. While it's not required, it does make the syntax easier to parse for the average developer coming from C# or Visual Basic and so it frequently appears in the F# samples and posted code snippets, making it the de facto standard for programming F#. (A future version of F# may actually make #light the default syntax, instead of the other way around.)
That seemingly innocent for loop is, in fact, anything but simple. Officially, this is a generated list, which is a fancy way of saying it is a block of code that will produce a result that is a list.
A list is a primitive construct found frequently in functional languages, and in that respect it is similar in many ways to an array. However, a list does not allow for position-based access (such as the traditional a[i] syntax in C#). Lists will show up in a variety of places in functional programming and, for the most part, can be thought of as the F# equivalent to the .NET Framework List<T> with some enhanced capabilities.
A list is always of some particular type, and in this case the identifier results is a list of tuples, specifically the tuple type identified by F# to be of type (int * int). This idea of a list of tuples is familiar if thought of as equivalent to a pair of columns returned by a SELECT statement in SQL. Thus, the example essentially creates a list of pairs of integers that is 100 items long.
Commonly, in functional languages, function definitions are used anywhere the code itself can appear. So, if you want to extend the previous example, you could write:
let compute2 x = (x, x*x)
let compute3 x = (x, x*x, x*x*x)
let results2 = [ for i in 0 .. 100 -> compute2 i ]
let results3 = [ for i in 0 .. 100 -> compute3 i ]
This idea of looping through a list (or array or some other iterable construct) is such a common task in functional languages that it has been generalized as a basic method call: List.iter. This simply calls a function on each element of the list. Other similar library functions provide useful capabilities. For example, List.map takes a function as an argument and applies the function to each element of the list, returning a new list in the process.
The Pipeline
Let's examine one more construct in F#—the pipeline operator—which takes the results of a function and, in a vein similar to pipes from command shells (like Windows PowerShell®), uses that as the input to a follow-up function. Consider the snippet of F# shown in Figure 2. This code uses the System.Net namespace to connect to an HTTP server, slurp down the corresponding HTML, and analyze the results.
Figure 2 Retrieving and Analyzing HTML
/// Get the contents of the URL via a web request
let http(url: string) =
let req = System.Net.WebRequest.Create(url)
let resp = req.GetResponse()
let stream = resp.GetResponseStream()
let reader = new System.IO.StreamReader(stream)
let html = reader.ReadToEnd()
resp.Close()
html
let getWords s = String.split [ ' '; '\n'; '\t'; '<'; '>'; '=' ] s
let getStats site =
let url = "https://" + site
let html = http url
let words = html |> getWords
let hrefs = html |> getWords |> List.filter (fun s -> s = "href")
(site,html.Length, words.Length, hrefs.Length)
Note the words identifier in the definition of getStats. It takes the html value returned from the URL and applies the getWords function to it. I could have also written the definition to read:
let words = getWords html
The two are identical. But the hrefs identifier shows off the power of the pipeline operator, in that you can string an arbitrary number of applications together. Here I take the resulting list of words and pipe it through the List.filter function, which takes an anonymous function to look for the word href and return it if the expression holds true. And, just to top it all off, the results of the getStats call will be another tuple, this one a (string * int *int * int). To write that using C# will take up much more than 15 lines of code.
The example in Figure 2 also shows off more of the compatibility of F# with the .NET Framework, a theme which you can see repeated here:
open System.Collections.Generic
let capitals = Dictionary<string, string>()
capitals.["Great Britain"] <- "London"
capitals.["France"] <- "Paris"
capitals.ContainsKey("France")
This is really nothing more than exercising the Dictionary<K,V> type, but it demonstrates how generics are specified in F# (using the angle brackets, just like in C#), how to use indexers in F# (using the square-bracket syntax, again just like in C#), and how to execute .NET methods (using the "dot" and parentheses, just like in C#). In fact, the only new bit here is how to do assignment of mutable values, which uses the left-arrow operator. This is necessary because F#, like most functional languages, reserves the use of the equals operator for comparison, in keeping with the mathematical notion that if x = y, then x and y are of the same value, not that y is being assigned to x. (Real mathematicians have been known to turn up their nose—or giggle hysterically—at the idea that x = x + 1 could be true in any universe real or imagined.)
F# Can Do Objects, Too
Of course, not all .NET developers coming to F# will want to immediately embrace the functional concepts. In fact, most developers coming to F# from C# or Visual Basic will need to know that they can fall back to old habits without breaking the language. And to an extent, they can.
Consider, for example, the class definition for the two-dimensional vector show at the top of Figure 3. A couple of interesting ideas come out of this. First, notice that there is no explicit constructor body; the parameters on the first line indicate the parameters by which people will construct Vector2D instances, essentially serving as a constructor. The length identifier, along with the dx and dy identifiers, thus become private elements inside the Vector2D type, while the member keyword indicates members that should be available outside of Vector2D, via standard .NET property access. In essence, this F# code declares what you see at the bottom of Figure 3 (as reported by Reflector).
Figure 3 Vector Variations in F# and C#
<strong xmlns="https://www.w3.org/1999/xhtml">VECTOR2D IN F#</strong>
type Vector2D(dx:float,dy:float) =
let length = sqrt(dx*dx + dy*dy)
member obj.Length = length
member obj.DX = dx
member obj.DY = dy
member obj.Move(dx2,dy2) = Vector2D(dx+dx2,dy+dy2)
<strong xmlns="https://www.w3.org/1999/xhtml">VECTOR2D IN C# (REFLECTOR></strong>
[Serializable, CompilationMapping(SourceLevelConstruct.ObjectType)]
public class Vector2D
{
// Fields
internal double _dx@48;
internal double _dy@48;
internal double _length@49;
// Methods
public Vector2D(double dx, double dy)
{
Hello.Vector2D @this = this;
@this._dx@48 = dx;
@this._dy@48 = dy;
double d = (@this._dx@48 * @this._dx@48) +
(@this._dy@48 * @this._dy@48);
@this._length@49 = Math.Sqrt(d);
}
public Hello.Vector2D Move(double dx2, double dy2)
{
return new Hello.Vector2D(this._dx@48 + dx2, this._dy@48 + dy2);
}
// Properties
public double DX
{
get
{
return this._dx@48;
}
}
public double DY
{
get
{
return this._dy@48;
}
}
public double Length
{
get
{
return this._length@49;
}
}
}
Remember that F#, like most functional languages, encourages immutable values and state. This should be obvious when looking at the code in Figure 3, since all the properties are read-only, and the Move member doesn't modify the existing Vector2D but instead creates a new one from the current Vector2D and applies the modifying values to it before returning it.
Notice, too, that the F# version is not only entirely thread safe but entirely accessible from traditional C# or Visual Basic code. This provides an easy way to get started with F#: using it to define business objects or other types that want or need to be thread safe and immutable. And while it is certainly possible to create types in F# that provide the usual set of mutable operations (set properties and the like), it requires more work and the use of the mutable keyword to do so. In a world where concurrency concerns are becoming the order of the day, this is exactly the way many argue it should be—immutable by default, mutable when necessary or desired.
Creating types in F# is interesting, but it's also possible to use F# to do what traditional C# or Visual Basic code can do, such as create a simple Windows Forms application and gather input from the user, as shown in Figure 4.
Figure 4 Windows Forms with F#
#light
open System
open System.IO
open System.Windows.Forms
open Printf
let form = new Form(Text="My First F# Form", Visible=true)
let menu = form.Menu <- new MainMenu()
let mnuFile = form.Menu.MenuItems.Add("&File")
let filter = "txt files (*.txt)|*.txt|All files (*.*)|*.*"
let mnuiOpen =
new MenuItem("&Open...",
new EventHandler(fun _ _ ->
let dialog =
new OpenFileDialog(InitialDirectory="c:\\",
Filter=filter;
FilterIndex=2,
RestoreDirectory=true)
if dialog.ShowDialog() = DialogResult.OK then
match dialog.OpenFile() with
| null -> printf "Could not read the file...\n"
| s ->
let r = new StreamReader(s)
printf "First line is: %s!\n" (r.ReadLine());
s.Close();
),
Shortcut.CtrlO)
mnuFile.MenuItems.Add(mnuiOpen)
[<STAThread>]
do Application.Run(form)
Any developer familiar with Windows Forms will be able to recognize fairly quickly what's happening here: a simple Form is created, some properties populated, an event handler filled, and the application is told to run until the user clicks the Close button in the upper-right corner. Standard stuff as .NET applications go, allowing focus to remain entirely on the F# syntax.
The open statement operates in much the same way as C#'s using statement, essentially opening a .NET namespace for use without formal qualifiers. The Printf namespace is an F# original, technically a port of the OCaml module of the same name. F# ships not only with full fidelity to the .NET Framework class library but also with a mostly straightforward port of the OCaml libraries, making programmers who are familiar with that language feel more at home with the .NET Framework. (For those who are curious, Printf lives inside of the assembly FSharp.Core.dll.) Nothing prevents you from using System.Console.WriteLine—it's a simple matter of aesthetic preference.
The creation of the form identifier makes use of F# named parameters, which is equivalent to instantiating the object and then making a series of property set calls to populate those properties with values. I do the same with the dialog identifier creates a few lines below that.
The definition of the mnuiOpen identifier contains an interesting construct, one which won't be too unfamiliar to developers familiar with anonymous delegates from the .NET Framework 2.0 or lambda expressions from the .NET Framework 3.5. In the construction of the EventHandler associated with the Open MenuItem, you see an anonymous function, defined using the syntax:
fun _ _ -> ...
Like anonymous delegates, this creates a function that will be invoked when the menu item is selected, but the syntax is a bit tricky.
The definition of the EventHandler part of the MenuItem definition is an anonymous function that ignores the two parameters passed to it, which neatly correspond to the sender and event arguments in the standard EventHandler delegate type. The function stipulates that a new OpenFileDialog is to be displayed and, when OK is clicked, the results examined ... somehow:
if dialog.ShowDialog() = DialogResult.OK then
match dialog.OpenFile() with
| null -> printf "Could not read the file...\n"
| s ->
let r = new StreamReader(s) in
printf "First line is: %s!\n" (r.ReadLine());
s.Close();
The results are examined using pattern matching, a powerful feature of the functional language world. Ostensibly similar in some ways to a switch/case from C#, pattern matching essentially does as its name implies: it compares a value with a variety of different patterns—not all of which need to be constant values—and executes the block of code that matches. So, for example, in the match block shown here, the result of OpenFile is matched against two possible values: null, meaning no file could be opened, or s, which is assigned any non-null value and then used as the constructor to StreamReader to open and read the first line of the given text file.
Pattern matching is a big part of most functional languages and deserves a bit of explanation. One of its most common uses is in conjunction with a discriminated union type, vaguely reminiscent of an enumerated type from C# or Visual Basic:
// Declaration of the 'Expr' type
type Expr =
| Binary of string * Expr * Expr
| Variable of string
| Constant of int
// Create a value 'v' representing 'x + 10'
let v = Binary("+", Variable "x", Constant 10)
This is commonly used in functional languages to create core representations of domain-specific languages that developers can use to write more complicated and powerful constructs. For example, it's not hard to imagine extending this syntax to create a full computation language that can easily be extended further simply by adding new elements to the Expr type. One thing to be careful of here; the syntax using the * character does not indicate that we use multiplication but is a standard way among functional languages to indicate a type that consists of multiple parts.
In fact, functional languages are commonly used to write language-oriented programming tools such as interpreters and compilers, with the Expr type eventually becoming the full set of language expression types. F# makes this even simpler by including two tools, fslex and fsyacc, specifically designed to take traditional language inputs—lex and yacc files—and compile them into F# code for easier manipulation. Download the F# installer to explore this if you're interested; in particular, the Parsing example in the standard F# distribution will provide a nice basic structure from which to get started.
The discriminated union is only one of the benefits of pattern matching; the second is the execution of the expressions, and you see that in Figure 5. The rec in the definition of eval is necessary to tell the F# compiler that eval will be invoked recursively within the body of the definition. Without it, F# will be expecting a local nested function named eval to be present. I use the function getVarValue to return some predefined values for the variables—in a real-world application, getVarValue would likely examine a Dictionary for the values to hand back, as established at the time the variable was created.
Figure 5 Expression Execution
let getVarValue v =
match v with
| "x" -> 25
| "y" -> 12
| _ -> 0
let rec eval x =
match x with
| Binary(op, l, r) ->
let (lv, rv) = (eval l, eval r) in
if (op = "+") then lv + rv
elif (op = "-") then lv - rv
else failwith "E_UNSUPPORTED"
| Variable(var) ->
getVarValue var
| Constant(n) ->
n
do printf "Results = %d\n" (eval v)
When eval is called, it takes the value v and discovers that it is a Binary value. This matches the first subexpression, which in turn binds the value (lv, rv) to the evaluated results of the left and right parts of the Binary value just examined. The unnamed value (lv, rv) is a tuple, essentially a single value of multiple parts, not unlike a relational set or C struct.
When eval l is first called, l from the Binary instance happens to be a Variable type, so the recursive call to eval matches against that branch of the pattern-matching block. This in turn calls getVarValue, which returns the hardcoded 25, which is ultimately bound against the value lv. The same sequence is executed for r, which is a Constant containing the value 10, so this is bound against rv. The remainder of the block is executed, an if/else-if/else block, which reads pretty easily for a developer familiar with C#, Visual Basic, or C++.
The key thing to recognize here is that, once again, every expression returns a value, even inside the pattern-matching block. In this case, the return value is an integer value, either the value of the operation, the value retrieved from the variable, or the constant itself. This single point, more than any other, seems to trip up developers more accustomed to object-oriented or imperative programming since, in C#, Visual Basic, or C++, having return values is optional and can be ignored even when specified. In functional languages such as F#, to ignore a return value requires an explicit coding idiom. In such cases, programmers can feed the results to a function called ignore, which does exactly as its name implies.
Asynchronous F#
Thus far, my dive into the F# syntax has taken one of two forms: either working with fairly simple functional constructs or making it look like a stranger—and more terse—variation on the traditional object-oriented, .NET-compliant languages (C#, Visual Basic, or C++/CLI). These make hardly a compelling argument for the adoption of F# within the enterprise.
But take a look at Figure 6. This most certainly does not fit either of the two previously mentioned categories. Aside from the ! characters that show up in a couple of places and the use of the async modifier, this appears to be relatively straightforward code: loading a source graphic image, extracting its data, passing that data to a standalone function for manipulation (rotation, skew, or whatever), and writing that data back into an output file.
Figure 6 Manipulating an Image
let TransformImage pixels i =
// Some kind of graphic manipulation of images
let ProcessImage(i) =
async { use inStream = File.OpenRead(sprintf "source%d.jpg" i)
let! pixels = inStream.ReadAsync(1024*1024)
let pixels' = TransformImage(pixels,i)
use outStream = File.OpenWrite(sprintf "result%d.jpg" i)
do! outStream.WriteAsync(pixels')
do Console.WriteLine "done!" }
let ProcessImages() =
Async.Run (Async.Parallel
[ for i in 1 .. numImages -> ProcessImage(i) ])
What's not obvious is that the use of the async modifier makes this code into what F# calls an asynchronous workflow (no relation to Windows Workflow Foundation), meaning that each of these load/process/save steps is taking place on parallel threads from a .NET thread pool.
Putting it more simply, consider the code in Figure 7. This particular sequence shows off async workflows but in a fairly simple and easy-to-swallow way. Without going into too much detail, evals is an array of functions waiting to be executed, each of which is queued for execution on the thread pool by the Async.Parallel call. When executed, it becomes apparent that the functions inside of evals are, in fact, on a separate thread from the function in awr (though due to the nature of the .NET system thread pool, some or all of the evals functions may be executed on the same thread).
Figure 7 Executing Functions Asynchronously
#light
open System.Threading
let printWithThread str =
printfn "[ThreadId = %d] %s" Thread.CurrentThread.ManagedThreadId str
let evals =
let z = 4.0
[ async { do printWithThread "Computing z*z\n"
return z * z };
async { do printWithThread "Computing sin(z)\n"
return (sin z) };
async { do printWithThread "Computing log(z)\n"
return (log z) } ]
let awr =
async { let! vs = Async.Parallel evals
do printWithThread "Computing v1+v2+v3\n"
return (Array.fold_left (fun a b -> a + b) 0.0 vs) }
let R = Async.Run awr
printf "Result = %f\n" R
The fact that these are executed out of the .NET thread pool highlights once again just how well the F# language supports interoperability with the underlying runtime. Its reliance on the .NET Framework class library even for areas traditionally reserved for specialized implementation (such as threading) in functional languages means that the C# programmer can make use of F# libraries or modules, just as F# developers make use of C# libraries. In fact, in the future, F# features such as async tasks will be able to take advantage of new .NET Framework libraries such as the Task Processing Library in the Parallel Extensions library.
Fitting in with F#
If it's not apparent already, there is much more to be said about the F# language than can be covered in a single article; in fact, between the new syntax and the entirely new way of thinking (functional vs. imperative), it's fair to say that for the average object-oriented developer accustomed to C# or Visual Basic, mastering F# will take some time. Fortunately, F# remains entirely interoperable with the rest of the .NET ecosystem, meaning you can take advantage of much existing knowledge and many existing tools to help bring F# into your coding arsenal.
All the foundation libraries are fully accessible to the F# developer, and, since F# supports some aspects of imperative and object development, it's fully feasible to consider using the F# interactive mode as a way of learning both the F# syntax and the details of Windows Presentation Foundation, Windows Communication Foundation, or Windows Workflow Foundation without having to pause for compile cycles.
As already mentioned, developers can write business objects in F# for use by other parts of their application code. Because the F# type construct produces classes that are, for the most part, identical to their C# or Visual Basic equivalents, persistence libraries like NHibernate will persist the F# types without trouble, making for a seamless injection of F# into working business applications.
Merely learning F# will help you comprehend many of the features of future versions of C# and Visual Basic, since many of those ideas and concepts—including generics, iterators (the yield keyword in C#), and LINQ—were born of functional roots and the research conducted by the F# team. Regardless of how you look at it, functional programming is here, and it's here to stay.
Ted Neward is an independent consultant specializing in high-scale enterprise systems. He is the author and coauthor of several books, a Microsoft MVP Architect, a BEA Technical Director, an INETA speaker, and a PluralSight instructor. Reach Ted at ted@tedneward.com or visit his blog at blogs.tedneward.com.