As mentioned in my previous post, I've been beavering away on the handout for a new class I'm delivering at this year's Autodesk University. Here is the first part of this handout.
Introduction
F# is a new programming language from Microsoft, due to become a first-class .NET citizen (joining its siblings C# and VB.NET) and fully integrated with Visual Studio 2010. In this class we’ll introduce many of the concepts behind the F# language, and look at examples where we use it to create applications inside AutoCAD.
At the time of writing, F# is available as a Community Technology Preview (CTP), installable on Visual Studio 2008. Prior versions of F# provide some integration with Visual Studio 2005, but in this session I’ll primarily be using the superior integration with Visual Studio 2008.
The various versions of F# are available from: http://research.microsoft.com/fsharp
For Visual Studio 2005, install 1.9.4.19
For Visual Studio 2008: install 1.9.6.2, the F# September 2008 CTP
For these samples, as well as other examples of integrating F# with AutoCAD, visit my blog: http://through-the-interface.typepad.com/through_the_interface/f
Creating our first F# project
Let’s start by creating an F# project and adding some very simple code.
In Visual Studio 2005, you’ll find the F# project type under “Other Project Types”, assuming you’ve installed a version of F# that integrates with VS 2005, of course:
Figure 1 – Creating a new F# project in Visual Studio 2005
In Visual Studio 2008 we can already see an example of a tighter integration - the F# project types are included at a higher level in the tree-view of project types:
Figure 2 – Creating a new F# project in Visual Studio 2008
The blank project in Visual Studio 2005 is just that – it doesn’t even include a source file. If you right-click the project in the solution explorer and Add -> New Item, you’ll get a dialog showing the various types of source file that you can add:
Figure 3 – Adding a new F# source file to our blank project in Visual Studio 2005
The initial F# source file created in Visual Studio 2005 already contains a lot of code intended to introduce you to various F# language features. To get the equivalent code in Visual Studio 2008, you need to create a “F# Tutorial” project type.
The standard file created by default in a new Visual Studio 2008 F# project – Program.fs – contains only this text, which we’ll use as a starting point:
#light
This directive tells the F# compiler that we’ll be using “light” syntax, which essentially means a lighter-weight version of the language that relies more heavily on indentation and reduces the need for additional keywords in your source files. The light syntax is very much the standard: almost all F# samples you’ll come across - except for those designed to show the heavyweight syntax – make use of this convention.
Before we write some code, let’s launch the “F# Interactive” (FSI) window in Visual Studio – a very useful explorative environment for developing and testing F# code.
In Visual Studio 2005, you’ll find this under the Add-In manager (Tools -> Add-in Manager):
Figure 4 – Launching F# Interactive in Visual Studio 2005
In Visual Studio 2008 this has, once again, become more tightly integrated:
Figure 5 - Launching F# Interactive in Visual Studio 2008
Once launched, you should see the FSI window at the bottom of Visual Studio. FSI is an integrated component that allows on-the-fly compilation and evaluation of F# code. It doesn’t actually interpret the code, but it can often feel as though that’s what’s happening.
OK, now we’re ready top write our first F# function. Type the following code into the F# code window:
let f a b = a + b
The let operator binds a value or a function to a symbol. In this case the function is called f, and takes two arguments, a and b. The result of this function is the sum of a and b.
You’ll probably notice something interesting about this function, right off the bat: there are no types declared. These are inferred by the system, based on how the code is used. The default will be to consider f to take two integers and return an integer, but if your code uses it with real numbers then the arguments will be inferred to be of type float.
It is also possible to specify the type of the arguments by saying, for instance:
let f (a : float) (b : float) = a + b
This can be useful, but makes your code less composable (i.e. harder to copy and paste and use elsewhere). Trusting F#’s type inference to determine the right types is the best approach, where possible: there is no performance penalty as the types are inferred at compile-time, not at runtime.
Before we see this thrilling function in action, let me point out a few more significant facts about it. This function is pure, which means a few things:
- Its outputs are a simple function of its inputs
- It attempts no side-effects
- No shared state is modified
- It doesn’t attempt to write to a file or to the screen
- If you execute this function multiple times with the same arguments, you will always get the same result
Pure code has a number of advantages: it can more easily be parallelized (farmed out to multiple computing cores), and is also more composable. Having whole programs that are pure isn’t realistic, though: some side-effects are needed to let the user know what’s happening, for instance :-), but having pure sections of code is going to become an increasingly important goal over the coming years.
OK, let’s now highlight our code and press Alt-Enter to load it into FSI:
Figure 6 – Loading our first F# function into F# Interactive
After loading the function we can test it by typing this at the command-line:
f 1 2;;
This calls the function f with arguments 1 and 2 (you can see the result above). You need to terminate code in the FSI window with a double semi-colon – if your code doesn’t appear to be executing properly, then it’s most likely you’re forgetting to do that.
You can see that the type of the function has been inferred as:
val f : int -> int -> int
This means it takes two integers and returns an integer. You may find the type format somewhat strange: there’s no real distinction between the arguments and the returned value, other than their order in the “pipeline”. This allows us to do things like partial application and currying – an example of partial application being if we define an “increment” function called inc:
let inc = f 1
Here we have just stated that calling inc on an integer is like calling f on 1 and that integer. It may seem strange – once again – that we don’t write it like this:
let inc a = f 1 a
In my view one of the beauties of functional programming is that this is not needed – you can define functions simply by “pinning down” arguments of other functions.
Creating our first AutoCAD application in F#
The code used in this example is available here for 1.9.6.19 and before and here for 1.9.6.2 onwards.
We’re here to find out how to create F# code that works with AutoCAD, so let’s go ahead and do that. We’re going to create an AutoCAD command that goes through the contents of our drawing and creates a list of all the words used (removing duplicates along the way). As you’ll see in this code, processing lists of data is very easy from a functional programming language.
At this point I’m going to stop explaining how to do most things in the pre-CTP versions of F# - a VS 2005-compatible version of the code has been posted to my blog, as there will now start to be an increasing number of differences both at the project level and in the source code that we’re going to create. Code written against the eventual released version of F# is much more likely to code written against the CTP, so that’s what we’ll focus on here.
A word of caution: F# Interactive is not hosted inside AutoCAD’s process, so calling F# functions that access AutoCAD objects via the .NET API from FSI will not work. That said, we will create some core functions that are capable of being tested within FSI, so there is some value to understanding how this is possible.
Let’s now make sure our project is going to create a Class Library (DLL) rather than a Windows Application (EXE). We do this by modifying the project properties (using either the main pull-down menus, Project -> FirstFSharpProject Properties…, or right-clicking the FirstFSharpProject project in the Solution Explorer and selecting Properties from the context menu).
Figure 7 – Changing our project settings to create a Class Library
Let’s now replace our code inside the main file (in my case called Program.fs) with this:
#light
// Partial application of split which can then be
// applied to a string to retrieve the contained words
let words =
String.split [' ']
What we have here is a function called words which is simply a call to the String.split function (a static function belonging to the String class – this is where we start to use F#’s object-oriented capabilities, as well as its functional ones). The words function still needs an argument, and, of course, we could also write it like this, if we chose:
let words text =
String.split [' '] text
But we won’t, as the previous definition is more elegant. :-)
OK, so let’s give this a try in FSI. Select the whole contents of the file and hit Alt-Enter.
All being well we should see an error such as this:
C:\...\ FirstFSharpProject\Program.fs(7,10): error FS0039: The value, constructor, namespace or type 'split' is not defined. A construct with this name was found in FSharp.PowerPack.dll, which contains some modules and types that were implicitly referenced in some previous versions of F#. You may need to add an explicit reference to this DLL in order to compile this code.
That’s actually a very accurate and helpful error message: to make use of String.split in our project we need to add a reference to it. And to use it in FSI we need to do the same.
Add a reference via the pull-down menu or the Solution Explorer, selecting this module:
Figure 8 – Adding a project reference to the F# PowerPack
That works for the project, but FSI will give us exactly the same error as before. Within FSI, type the following instruction, which will add a reference to the assembly (and is, in fact, the easiest way to add references to your project when working with pre-CTP F#):
#r "FSharp.PowerPack";;
We should get this response:
--> Referenced 'C:\Program Files\FSharp-1.9.6.2\bin\FSharp.PowerPack.dll'
And when we attempt to reload the code into FSI using Alt-Enter, we should see:
val words : string -> string list
Which means we have a function called words that takes a string and returns a list of strings. Let’s give it a try:
> words "The quick brown fox jumped over the lazy dog.";;
val it : string list
= ["The"; "quick"; "brown"; "fox"; "jumped"; "over"; "the"; "lazy"; "dog."]
>
Great! But right now we’re only using the space character to separate words. There are a number of characters that make sense to use as separators in the context of text found in AutoCAD drawings, for example, along with space, I would also choose tab and a number of symbols such as: ~`!@#$%^&*()-=_+{}|[]\;':"<>?,./
So how do we do that? Well, the ugly way is to pass that in as a list of characters into our function:
let words =
String.split [' ';'\t';'~';'`';'!';'@';'#';'$';'%';'^';'&';'*';'(';')';'-';'=';'_';'+';'{';'}';'|';'[';']';'\\';';';'\'';':';'\"';'<';'>';'?';',';'.';'/']
Yes, that’s certainly ugly. A better approach would be to just have a string that we decompose into a list (a relatively inexpensive operation that is insignificant when compared with the code maintenance benefits).
Let’s create an intermediate value called seps and assign our string to it. We can then use the string’s ToCharArray() method to generate a nice array of characters, which we then turn into a list using Array.to_list:
let words =
let seps = " \t~`!@#$%^&*()-=_+{}|[]\\;':\"<>?,./"
String.split (Array.to_list (seps.ToCharArray()))
It should be obvious how we’re using brackets to choose the execution order of the above code.
To test this out, let’s pass in a few paragraphs from this document, to see how it copes:
> words "We’re here to find out how to create F# code that works with AutoCAD, so let’s go ahead and do that. We’re going to create an AutoCAD command that goes through the contents of our drawing and creates a list of all the words used (removing duplicates along the way). As you’ll see in this code, processing lists of data is very easy from a functional programming language.
At this point I’m going to stop explaining how to do most things in the pre-CTP versions of F# - a VS 2005-compatible version of the project has been posted to my blog, as there will now start to be an increasing number of differences both at the project level and in the source code that we’re going to create. Code written against the eventual released version of F# is much more likely to code written against the CTP, so that’s what we’ll focus on here.
A word of caution: F# Interactive is not hosted inside AutoCAD’s process, so calling F# functions that access AutoCAD objects from FSI will not work. That said, we will create some core functions that are capable of being tested within FSI, so there is some value to understanding how this is possible.
Let’s now make sure our project is going to create a Class Library (DLL) rather than a Windows Application (EXE). We do this by modifying the project properties (using either the main pull-down menus, Project -> FirstFSharpProject Properties…, or right-clicking the FirstFSharpProject project in the Solution Explorer and selecting Properties from the context menu).";;
val it : string list
= ["We’re"; "here"; "to"; "find"; "out"; "how"; "to"; "create"; "F"; "code";
"that"; "works"; "with"; "AutoCAD"; "so"; "let’s"; "go"; "ahead"; "and";
"do"; "that"; "We’re"; "going"; "to"; "create"; "an"; "AutoCAD"; "command";
"that"; "goes"; "through"; "the"; "contents"; "of"; "our"; "drawing"; "and";
"creates"; "a"; "list"; "of"; "all"; "the"; "words"; "used"; "removing";
"duplicates"; "along"; "the"; "way"; "As"; "you’ll"; "see"; "in"; "this";
"code"; "processing"; "lists"; "of"; "data"; "is"; "very"; "easy"; "from";
"a"; "functional"; "programming"; "language"; "\nAt"; "this"; "point";
"I’m"; "going"; "to"; "stop"; "explaining"; "how"; "to"; "do"; "most";
"things"; "in"; "the"; "pre"; "CTP"; "versions"; "of"; "F"; "a"; "VS";
"2005"; "compatible"; "version"; "of"; "the"; "project"; "has"; "been";
"posted"; "to"; ...]
>
The display of the returned value has been truncated to the first 100 elements, but it is all there. You may also see additional characters that could be added to the list of separators, but that is left as an exercise for the reader. :-)
So far the words we have are neither sorted nor de-duplicated. Let’s now look into doing that, by defining a new sortedWords function that takes a list of strings (as it’s going to make sense later to pass in a list of strings, rather than just having one monolithic string as the input) and returns a sorted, de-duplicated list of all the words contained within.
let sortedWords x =
x |>
List.map words |> // Get the words from each string
List.concat |> // No need for the outer list
Set.of_list |> // Create a set from the list
Set.to_list // Create a list from the set
We have a new operator in the above code, and it’s one you’ll see a lot in F# code: the pipeline operator, “|>”. This operator is actually conceptually very simple, but makes it possible to create elegant code where the data flows from the beginning of the pipeline to its end, going from function to function.
This function is actually equivalent to this:
let sortedWords x =
List.map words x |> // Get the words from each string
List.concat |> // No need for the outer list
Set.of_list |> // Create a set from the list
Set.to_list // Create a list from the set
The choice is really about readability. To understand the pipeline operator, it can help to see its definition:
let (|>) x f = f x
This means our code is actually equivalent to the (in my opinion) less readable:
let sortedWords x =
Set.to_list (Set.of_list (List.concat (List.map words x)))
If you look back to the definition of words, we see it follows a similar form. Let’s re-write it to use pipelines:
let words =
let seps = " \t~`!@#$%^&*()-=_+{}|[]\\;'’:\"<>?,./"
seps.ToCharArray() |> Array.to_list |> String.split
Now let’s go back to our sortedWords definition. In all versions of the function we start by passing our only argument – x, a list of strings – into a function that “maps” a function across a list, creating a list containing the results of the various operations. Here’s a simple example using the inc function we created earlier:
let f a b = a + b
let inc = f 1
let x = List.map inc [1; 2; 3; 4; 5; 6; 7]
When we load the code into FSI and check the value of x, we see:
val f : int -> int -> int
val inc : (int -> int)
val x : int list
> x;;
val it : int list = [2; 3; 4; 5; 6; 7; 8]
As you can see, the map function applies a function (in our case inc) to each member of the input list and has placed the results in an output list of the same length.
If we really wanted to get clever we could use a lambda (i.e. anonymous) function to avoid having to define inc in the first place:
let x = List.map (fun i -> i+1) [1; 2; 3; 4; 5; 6; 7]
Lambda functions are just one example of a functional programming concept that has made its way into a mainstream, general-purpose language such as C#.
Back to our pipeline... as we’re mapping the words function to a list, and the words function itself returns a list, each time it is called, we’re going to have a list of lists of strings on our hands. So we’re going to call List.concat to concatenate the contents of the outer list – we therefore end up with a single list of all the words across our strings.
This “gross” list of words then gets passed into the Set.of_list function, which creates a set from our list, and sorts/de-duplicates it in the process. Very handy.
As we want to get back to a list of words on the output, we simply call Set.to_list on our set to generate our “net” list.
Here’s the sortedWords function working on a simple list of strings:
> sortedWords ["this is my first sentence"; "this is my second sentence"; "this is my third and final sentence"];;
val it : string list
= ["and"; "final"; "first"; "is"; "my"; "second"; "sentence"; "third"; "this"]
Now all that remains is to create a command inside AutoCAD which passes the various objects in a drawing to this function.
To be continued... :-)