C#/F# Interop and TDD

Since this development was supposed to conform to the best methodology available, I naturally chose Test Driven Development (TDD), which, as the name suggests, presumes writing tests for each piece of the code written. Actually it suggests writing tests first, but who wants to be so dogmatic! I was writing tests at least at the same time as I was developing.

Because functions in F# are first-class values, it is easier than in any other language to split the problem into smaller pieces, implement them, and then throw them around. This is where TDD really shines, because as you write smaller pieces it is easier to write tests for them (since test paths explode exponentially relative to the paths in code being tested). After small pieces have been tested, you proceed with confidence to “lego-izing” them into larger ones.

For this project, I did not want to learn any new unit test framework and just went with the Visual Studio one. Calling F# functions and objects from C# is pretty straightforward for the most part, except when F# functions come into play.

Here is a test for Stack functionality, for example:

using push.stack;
.....
.....
 [TestMethod]
 [Description("Tests pushStack/pop")]
 public void PushPopTest()
 {
     Stack.Stack<string> stack = Stack.empty<string>();
     stack = Stack.pushStack("a", stack);
     stack = Stack.pushStack("b", stack);
            
     var res = Stack.pop(stack);

     Assert.AreEqual<string>("b", res.Item1);

     res = Stack.pop(res.Item2);

     Assert.AreEqual<string>("a", res.Item1);

 }

here push.stack is the namespace in the Push.Core assembly that implements Push stack.

“pushStack” and “pop” are defined as follows:

    let pushStack hd tl = 
        match tl with
        |StackNode(x) -> StackNode(hd::x)

and

    let pop = function
        | StackNode([]) -> Unchecked.defaultof<'a>, StackNode([])
        | StackNode(hd::tl) -> hd, StackNode(tl)

(pop function is defined to do nothing in case of an empty stack. It is convenient to do so for Push, which is an absolutely robust language, i.e. any syntactically correct program is guaranteed to execute).

Intellisense also does a great job of guiding you to write correct code.

It becomes slightly more complicated when F# lambda expression need to be used. (Digression. At the Seattle F# meetup recently, someone suggested syntactic improvement to F#: to make the “fun” keyword unnecessary. After all it is kind of ironic, that lambda-syntax in C#, which is an imperative language, is simpler than the one in F#!)

In any event, I found .NET Reflector to be quite useful to quickly arrive at what I needed.

For instance:

let population = List.init config.populSize (fun i -> Code.rand (config.maxCodePoints, "INTEGER"))

became

this.population = 
   ListModule.Initialize<Ast.Push>(config.populSize, 
       FSharpFunc<int, Ast.Push>.FromConverter(i => Code.rand(config.maxCodePoints, FSharpOption<string>.None)));

in the tests.

The Reflector shows the following signature of the F# “init” function:

[CompilationArgumentCounts(new int[] { 1, 1 }), CompilationSourceName("init")]
public static FSharpList<T> Initialize<T>(int length, FSharpFunc<int, T> initializer)
{
    return List.init<T>(length, initializer);
}

We then click on FSharpFunc<int, T> and get the following definition:

[Serializable, AbstractClass, CompilationMapping(SourceConstructFlags.ObjectType)]
public abstract class FSharpFunc<T, TResult>
{
    // Methods
    protected FSharpFunc();
    public static FSharpFunc<T, TResult> FromConverter(Converter<T, TResult> converter);
    public abstract override TResult Invoke(T func);
    public static V InvokeFast<V>(FSharpFunc<T, FSharpFunc<TResult, V>> func, T arg1, TResult arg2);
    public static W InvokeFast<V, W>(FSharpFunc<T, FSharpFunc<TResult, FSharpFunc<V, W>>> func, T arg1, TResult arg2, V arg3);
    public static X InvokeFast<V, W, X>(FSharpFunc<T, FSharpFunc<TResult, FSharpFunc<V, FSharpFunc<W, X>>>> func, T arg1, TResult arg2, V arg3, W arg4);
    public static Y InvokeFast<V, W, X, Y>(FSharpFunc<T, FSharpFunc<TResult, FSharpFunc<V, FSharpFunc<W, FSharpFunc<X, Y>>>>> func, T arg1, TResult arg2, V arg3, W arg4, X arg5);
    public static implicit operator Converter<T, TResult>(FSharpFunc<T, TResult> func);
    public static implicit operator FSharpFunc<T, TResult>(Converter<T, TResult> converter);
    public static Converter<T, TResult> ToConverter(FSharpFunc<T, TResult> func);
}

It is far from straightforward, but the FromConverter method is of interest. Its argument Converter is defined as:

public delegate TOutput Converter<in TInput, out TOutput>(TInput input);

which is exactly what we want.

So, our F# line above becomes:

this.population = 
    ListModule.Initialize<Ast.Push>(config.populSize, 
       FSharpFunc<int, Ast.Push>.FromConverter(i => Code.rand(config.maxCodePoints, FSharpOption<string>.Some("INTEGER"))));

(Code.rand is defined as:

static member rand (maxPoints, ?bias : string)

“bias” argument is optional, which allows for its omission in F# code when calling the function. It compiles into the argument of option type, hence FSharpOption in the C# code.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.