Introduction
After procrastinating, I finally decided to sit down and learn to program in F#, since functional programming has always intrigued me. Although I had some experience with Prolog many moons ago, I have done most of my programming using imperative rather than functional or declarative languages, with recent work mostly in C#. Whenever I've learned new languages, there is always an effort required to "get up to speed", but with F#, I found the initial steps were more difficult than I anticipated, due to the lack of a mature IDE, on top of learning a new language and adjusting to a different programming paradigm. I'm still no expert in F#, but perhaps, some of my early stumbling might be of use to others, so they don't have to go through the same pain that I did.
This article describes my steps in getting F# to work for desktop applications. First, I'll describe how I got F# installed with VS 2008, and note some general program and architecture related issues that I ran across. Next, I'll present a simple Windows Forms application that can be used as a template for desktop applications written in F#. Finally, I'll summarize my perceptions of F# as a language, mention the strengths and weaknesses of the current implementation, and explain where I think F# fits in my personal .NET programming toolbox.
As an example application, I put together a simple parser test to be used in an expert system based on fuzzy logic. The parser simply accepts text input, and defines variables and fuzzy sets. The application allows text to be saved into and read from a text file, and is used only in my testing, so it isn't much useful in terms of fuzzy logic. For anyone interested, the actual parser and fuzzy set code is contained in the sample project, but won't be discussed here, since it is beyond the scope of this article. Perhaps, in a future article, I'll describe the rest of the system and show a real word application.
By way of background, the parser is meant to parse a fuzzy set and variable definitions. A variable is declared using either of these forms:
Variable Name of Context [=] Value (i.e. Variable Depth of Water = 250)
Variable Context Name [=] Value (i.e. Variable Water Depth 250)
and a fuzzy set is declared by the form:
FuzzySet Name Context [=] Values (i.e. FuzzySet Deep Water (0,0) (900,1)
In both of the definitions, both a Name
and a Context
are specified so that ideas with similar names can be differentiated, for example, a Hot Day vs. a Hot Volcano. Both can be measured in temperature units, and have the same name, but the ranges and the meaning of the "Hot" concept is very different.
Getting F# Up and Running
To get started with F#, I downloaded the Microsoft F# CTP (Version 1.9.6.2) from the Microsoft F# website[^], saving the .msi file and then running it to install F#. I accepted all the installation defaults, and it installed without any obvious problems.
Once installed, I opened VS 2008 Professional, and tried to activate the F# add-in according to the Microsoft instructions, but the F# add-in was not on the list under Tools | Add-in Manager. After uninstalling and reinstalling with the same results, I finally discovered that by simply typing Alt+Ctrl+F, the F# Interactive window opened, and apparently the installation had worked just fine the first time, an hour or so earlier. The F# add-in still doesn't show up in the Add-in Manager, but it seems to work just fine.
Once installed and working, using the VS editor with the F# Interactive tool was easy. By simply typing code in the editor, highlighting it, and pressing Alt+Enter, the highlighted code is copied into the interactive F# window, compiled, and run. It's beautiful for trying, testing, and interactively debugging code snippets. Also, in the editor, Intellisense seems to work for most, but not all, of the F# code that I needed.
In trying to code anything more than some trivial tests, however, I ran into two problems which I haven't really found mentioned elsewhere online.
First, even though I was able to pop-up a Windows form, I always had the black DOS/Command window show up as well. Since the F# system doesn't allow you to add a Windows Forms project, you have to manually go to the Project | Properties and set the application type to Windows Application. It appears that the default F# project is always a Console Application.
Second, even though I added references to the standard .NET namespaces (open System.Windows.Forms
, etc.), the F# compiler always complained about not finding things like System.Windows.Forms
, System.Windows.Drawing
, etc. Apparently, VS doesn't automatically add the common references, and those had to be added manually under Solution Explorer | References. With those two items out of the way, using F# was fairly easy, with the rest of my headaches being due to learning a new language and programming paradigm.
Once my project got to the point where I needed several source code files to keep things organized, I ran into several other problems. First, there is no obvious entry point, main function, or other obvious starting place in F# programs. So, how does the compiler know where to start?
Apparently, the F# system simply runs all of the executable statements in the last file compiled. This has two immediate implications:
- The order of the files in the Solution Explorer makes a difference.
- Make sure the starting point for your program is in the last file in the project list.
To work around these issues, I took the approach of placing each type in a separate file, pretty much like in C#, and creating a simple file that always is last in the file list called project_name.fs, where project_name is replaced by the actual name of my project. This file is very simple, containing only the following lines, where MyNamespace
and MyMainForm()
are the namespace and main form names used in a particular application. Note that if you need to do additional things before actually displaying the main form, the code for those can also be inserted before the do Application.Run
statement, and might require additional open
statements to reference any needed namespaces or modules.
#light
open System
open System.Windows.Forms
open MyNamespace
[<STAThread>]
do Application.Run(new MyMainForm())
Note that the [<STAThread>]
line is a .NET attribute that defines the application to run in a Single Thread Apartment. This is required when some of the .NET dialogs are used, such as FileOpenDialog
and FileSaveDialog
, because they apparently use COM Interop behind the scenes. If you don't use any of those, that attribute won't be needed.
Building a Windows Forms Application
In working with F#, I truly began to appreciate the designers that are available for C#, VB, and other languages supported by VS 2008 and other IDEs. Since form designers are not available for F#, I had to code all windows by hand. To keep things simple (for me), I adopted a coding style illustrated below for part of the MainForm
that represents the application.
#light
namespace MyNamespace
open System
open System.Windows.Forms
open System.Drawing
type MainForm() as form =
inherit Form()
let mutable fuzzySets = []
let mutable fuzzyRules = []
let mutable fuzzyVariables = []
let mutable fileName = ""
let mainMenu = new MainMenu()
let mnuFile = new MenuItem()
let mnuFileOpen = new MenuItem()
let mnuFileSave = new MenuItem()
let mnuFileSaveAs = new MenuItem()
let mnuFileExit = new MenuItem()
let mnuHelp = new MenuItem()
let mnuHelpAbout = new MenuItem()
let label1 = new Label()
let label2 = new Label()
let label3 = new Label()
let lstFuzzySets = new ListBox()
let lstVariables = new ListBox()
let txtInput = new RichTextBox()
let btnCalculate = new Button()
let dlgFileOpen = new OpenFileDialog()
let dlgFileSave = new SaveFileDialog()
let HomeDir = Application.ExecutablePath
let rec getVariable ((lst:(Variable list)), vName:string, vContext:string) =
match lst with
| [] -> failwith (sprintf "Variable %s.%s not found" vName vContext)
| x::_ when (x.Name = vName) && (x.Context = vContext) -> x
| _::t -> getVariable(t, vName, vContext)
let rec getFuzzySet ((lst:(FuzzySet list)), vName:string, vContext:string) =
match lst with
| [] -> failwith (sprintf "FuzzySet %s.%s not found" vName vContext)
| x::_ when (x.Name = vName) && (x.Context = vContext) -> x
| _::t -> getFuzzySet(t, vName, vContext)
let rec prtVars (s:(Variable list)) =
match s with
| [] -> ""
| x::y -> (sprintf "[%s %s = %f]" x.Name x.Context x.Value)^(prtVars y)
let rec prtFSets (s:(FuzzySet list)) =
match s with
| [] -> ""
| x::y -> (sprintf "[%s %s = %A]" x.Name x.Context x.Def)^(prtFSets y)
do form.InitializeForm
member this.InitializeForm =
this.FormBorderStyle <- FormBorderStyle.Sizable
this.Text <- "Fuzzy Logic Parser F# Test"
this.Width <- 300
this.Height <- 300
this.Load.AddHandler(new System.EventHandler
(fun s e -> this.Form_Loading(s, e)))
this.Closed.AddHandler(new System.EventHandler
(fun s e -> this.Form_Closing(s, e)))
mnuFile.Text <- "&File"
mnuFileOpen.Text <- "&Open"
mnuFileOpen.Click.AddHandler(new System.EventHandler
(fun s e -> this.mnuFileOpen_Click(s, e)))
mnuFileSave.Text <- "&Save"
mnuFileSave.Click.AddHandler(new System.EventHandler
(fun s e -> this.mnuFileSave_Click(s, e)))
mnuFileSaveAs.Text <- "Save &As"
mnuFileSaveAs.Click.AddHandler(new System.EventHandler
(fun s e -> this.mnuFileSaveAs_Click(s, e)))
mnuFileExit.Text <- "E&xit"
mnuFileExit.Click.AddHandler(new System.EventHandler
(fun s e -> this.mnuFileExit_Click(s, e)))
mnuFile.MenuItems.AddRange([| mnuFileOpen; mnuFileSave;
mnuFileSaveAs; mnuFileExit |])
mnuHelp.Text <- "&Help"
mnuHelpAbout.Text <- "&About"
mnuHelpAbout.Click.AddHandler(new System.EventHandler
(fun s e -> this.mnuHelpAbout_Click(s, e)))
mnuHelp.MenuItems.AddRange([| mnuHelpAbout |])
mainMenu.MenuItems.AddRange([| mnuFile; mnuHelp |])
this.Menu <- mainMenu
label1.Text <- "Fuzzy Sets"
label1.Location <- new Point(5,2)
label1.Dock <- DockStyle.None
label1.AutoSize <- true
lstFuzzySets.Location <- new Point(5,19)
lstFuzzySets.Width <- 137
lstFuzzySets.Height <- 120
lstFuzzySets.MouseDoubleClick.AddHandler(new MouseEventHandler
(fun s e -> this.lstFuzzySets_Click(s, e)))
...
this.Controls.AddRange([|
(label1:> Control);
(lstFuzzySets:> Control);
(label2:> Control);
(lstVariables:> Control);
(label3:> Control);
(txtInput:> Control);
(btnCalculate:> Control)
|])
member this.Form_Loading(sender : System.Object, e : EventArgs) =
lstFuzzySets.Items.Clear()
lstVariables.Items.Clear()
member this.Form_Closing(sender : System.Object, e : EventArgs) =
null
member this.mnuFileOpen_Click(sender : System.Object, e : EventArgs) =
dlgFileOpen.DefaultExt <- "txt"
if dlgFileOpen.ShowDialog() = DialogResult.OK then
fileName <- dlgFileOpen.FileName
txtInput.Clear()
txtInput.LoadFile(fileName)
else null
...
member this.mnuHelpAbout_Click(sender : System.Object, e : EventArgs) =
(new AboutForm()).ShowDialog() |> ignore
member this.lstFuzzySets_Click(sender : System.Object, e : MouseEventArgs) =
let s = String.split ['.'] (lstFuzzySets.SelectedItem.ToString())
let text (x:FuzzySet) = sprintf "%s = %A" (x.Name^"."^x.Context) (x.Def)
MessageBox.Show(text (getFuzzySet(fuzzySets, s.Head, s.Tail.Head))) |> ignore
...
Note that the form is defined by inheriting a standard .NET Form
. I then defined any private "variables" that will be needed, then all of the controls that are placed on the form. After the controls, I placed the constructor code that is to be executed; in this case, it simply calls the form.InitializeForm
function to initialize the form. Finally come all of the member functions that are needed, including all of the event handlers.
Inside the InitializeForm
member, each control is set up as needed, and finally all of the controls are added to the form. In addition, all events are defined as members so that they may be organized, coded separately, and called from outside the form. This is similar to how things are automatically organized by the C# and VB designers. Presumably, when designers for F# become available, they would take care of organizing things in a similar way. I have to admit that after doing the controls layout manually, I certainly appreciate the ability to use the designers available for other languages!
There are a couple of things to note in the above code.
First, the event handlers are added to the form and each control using the standard .NET AddHandler
, and are defined using anonymous functions in F#. Most of the event handlers take two parameters corresponding to the sender
and the EventArgs
, denoted by s
and e
in the anonymous functions.
Second, all of the event handlers expect a unit
(void
in C#) to be returned. In cases where the result of an F# function is a value other than unit
, it's necessary to either explicitly return a unit
value using the null
keyword or throw away a value using |> ignore
.
As you can see, there is really no true functional programming contained in the above code; it's all pretty much straight imperative code, similar to what would be used in C#, with a different syntax. That is mainly because of the dependence on the .NET framework that is inherently imperative. Of course, the ability of F# to handle both imperative and declarative code is one of its strengths, and the ability to program Windows forms shows the imperative side of F#.
For anyone interested, the actual parser and fuzzy set code is contained in the sample project, but won't be discussed here, since it is beyond the scope of this article. Perhaps, in a future article, I'll describe those parts of the system.
Conclusions
After experimenting with F# for awhile and getting somewhat up-to-speed, I have definitely formed some opinions. First, I have to say that F# appears to be a very nice, declarative, functional language, and should find wide application in many areas of scientific and mathematical computing. Since it also allows imperative programming, it is fairly easy to access the objects and features in the underlying .NET Framework, which is a big plus.
Unfortunately, F# in its current implementation is substantially lacking in what I would call "developer tools". Specifically, the lack of form designers and complete Intellisense support in Visual Studio makes it somewhat tedious to use. In addition, some of the default project settings are not very intuitive, and not extremely well documented. Hopefully, both of those problems will be addressed in the near future and F# will be able to take its place along with the other .NET languages as a serious development language.
Aside from the lack of developer tools, programming in F# takes some getting used to, especially if you are used to using imperative languages, as I am. The shift in paradigm from imperative to declarative can be difficult, and F# seems to have so many detailed oddities, that it indeed takes some concerted effort to master. I certainly have not yet mastered it, but I hope to get a book or two on the language on my next trip home and pursue F# much more in the future, because I do definitely see where it will be a handy tool to have in my programming toolbox.
I would recommend that anyone with even the slightest interest learn about F#. It never hurts to expand one's resume, or to stretch the brain into thinking about things from a different perspective.
In addition, I sincerely hope that Microsoft will continue to improve F#, and that additional project templates, form designers, and complete Intellisense support will be provided in the near future. And for my wish list, I'd have to add that being able to design and implement forms in C# (or even VB?), yet call functions written in F# would be wonderful. So far, the only way I've been able to do that has been to compile F# code to a library (*.dll) and then call it from C#. Being able to easily include both C# and F# files in a project, each compiled with the respective compiler and then linked into one application, would allow one to enjoy the best of both worlds.
History
- 24th October, 2008 - Article submitted; sure enough, right after posting, I had to correct a typo.