Gotchas: Common Traps for the F# n00b
Sunday, January 3, 2010 – 6:37 PMI spent a bunch of time over the holidays getting to know F# a bit better. I think I now consider myself to be truly dangerous with it.
A couple of things which repeatedly bit me as I stumbled through learning F# as a n00b.
Don’t forget the ‘=’ or the “rec”
First the dumb stuff…
A couple of things I found myself doing a lot when I first started. Most often was forgetting the ‘=’ at the end of the first line of a let function binding. The IDE will show an error for this, usually “Unexpected …. in binding. Expected ‘=’ or other token”.
Similarly recursive functions need to be labeled correctly. In this case the missing rec caused a “The value or constructor … is not defined” error for each reference to my recursive function. As with missing ‘=’ I found myself staring stupidly at the code trying to figure out what was wrong.
Module ordering matters
The order your F# source files appear in the solution matters! The project shown on the right doesn’t compile because the file OctreeTests.fs includes an open statement which refers to the module defined in Octree.fs. But—and here’s the gotcha—the tests file appears first in the list and thus gets compiled first! You’ll see an error on the open statement; “The namespace or module … is not defined”.
You can fix this by right clicking on either file and using the “Move Up” or “Move Down” menu items to correct the order. If you come from a C# background this isn’t the expected behavior.
Equality means structural equality
Equality in F# is treated a bit differently than C#. Consider the following two unit tests which compare lists. First in C#; two identical lists evaluate as not equal.
1: [Fact]
2: public void Test()
3: {
4: var list1 = new List<int>(new int[] { 1, 2, 3 });
5: var list2 = new List<int>(new int[] { 1, 2, 3 });
6:
7: Assert.False(list1 == list2);
8: }
Now lets do the same with F#. In this case the lists evaluate as equal.
1: [<Fact>]
2: let Test() =
3: let list1 : int list = [ 1; 2; 3 ]
4: let list2 : int list = [ 1; 2; 3 ]
5:
6: Assert.True((list1 = list2))
In other words F# uses structural equality not referential equality by default. In most cases this is probably what you want but in some cases it may not be. For instance if the lists are very long then this may be time consuming so in some cases you might want to provide a referential equality operator if your algorithm is OK with comparing instances not their values.
1: type LeafData =
2: { Boundary : OctantBoundary;
3: Properties : CenterOfMass;
4: Bodies : Body[]; }
5:
6: static member inline (==) (a : LeafData, b : LeafData) =
7: LanguagePrimitives.PhysicalEquality a b
There are some good blog posts on this by Don Syme and Brian McNamara if you want to get into more detail. These are definitely worthwhile reading.
Test driven development with TestDriven.NET and xUnit
One of the best things I discovered about F# was that xUnit.net now supports Fact attributes on static methods. Thanks to Harry and Matt for prompting Brad and Jim to add this to the latest versions of xUnit.net. This means you can write code like this right in your F# project:
1: [<Fact>]
2: let CreateOctantBoundaryReordersMinMax() =
3: let Max = VectorFloat(1.0, 1.0, 1.0)
4: let Min = VectorFloat(-1.0, -1.0, -1.0)
5:
6: let result = OctantBoundary.create Min Max
7:
8: Assert.Equal(Max, result.Max)
9: Assert.Equal(Min, result.Min)
If you use TestDriven.NET as your test runner then you might be tempted to right click on a test and try and run it.
Turns out that TestDriven.NET can get a bit confused by modules and namespaces. If you specify both the module and the namespace in your tests file TD.NET will give you an error something like “TestCase ‘T:Tests’ failed: Couldn’t find type with name ‘Tests’”.
1: #light
2:
3: namespace NBody.DomainModel.FSharp
4:
5: open NBody.DomainModel
6: open Xunit
7:
8: module Tests =
The workaround for this is to specify the module name only, not the namespace and a module.
1: #light
2:
3: module NBody.DomainModel.FSharp.Tests
The right click context menu should now work.
On a similar note; the Beta 2 drop of F# still includes the bug around decorating properties on interfaces which I blogged about a while back.
3 Trackback(s)
Sorry, comments for this entry are closed at this time.