Saturday, September 12, 2009

More F# under the covers

This is a bit of a follow up to

and was provoked by running NDepend over -- naturally -- my current .net project as a first test drive (and being a work in progress, the debug build). Which then it highlit a number of quite simple methods involving function chaining as being candidates for refactoring (as well as the tut-tutting at the code generated for the Either< 'a,'b > type).

Let's look that series of short-circuitable tests, mentioned in the first post, something that would look like

Test1(arg1, arg2)
&& Test2(argA, arg1, arg2)
&& Test3(arg1, arg2)
&& ...
view raw gistfile1.cs hosted with ❤ by GitHub

in C#.

Let's have a type that expresses the intent more directly than a general purpose boolean, and a couple of helpful combinators...

type internal Inconclusive =
| Resolved
| Open
let ignore _ = ()
let (>+??) (a:'a) (b:'b) =
(a, b, Open)
let (>?>) (arg : ('a * 'b * Inconclusive)) ( f : 'a -> 'b -> Inconclusive ) : ('a * 'b * Inconclusive) =
let (a,b,c) = arg
match c with
| Resolved -> arg
| Open -> (a, b, (f a b))
view raw gistfile1.fs hosted with ❤ by GitHub

Then we can write the chained tests as

arg1 >+?? arg2
>?> Test1
>?> (Test2 argA)
>?> Test3
...
|> ignore
view raw gistfile1.fs hosted with ❤ by GitHub

Reflector shows us that the IL that comes out of the process in a debug build corresponds to code that looks like

Tuple<A, B, Inconclusive> tuple = Module.op_GreaterPlusQmarkQmark<A B>(arg1, arg2);
Tuple<A, B, Inconclusive> tuple2 = Module.op_GreaterQmarkGreater<A, B>(tuple.Item1, tuple.Item2, tuple.Item3, new $DefiningType.DefiningMethod@line());
C temp = argA;
Tuple<A, B, Inconclusive> tuple3 = Module.op_GreaterQmarkGreater<A, B>(tuple2.Item1, tuple2.Item2, tuple2.Item3, new $DefiningType.DefiningMethod@line-1(temp));
Tuple<A, B, Inconclusive> tuple3 = Module.op_GreaterQmarkGreater<A, B>(tuple2.Item1, tuple2.Item2, tuple2.Item3, new $DefiningType.DefiningMethod@line-2());
...
Module.ignore<A, B, Inconclusive>(Module.op_GreaterQmarkGreater<A, B>(tupleN.Item1, tupleN.ItemN, tupleN.
view raw gistfile1.cs hosted with ❤ by GitHub

If the Test function being used is a member function of the class, you get temporaries like

MyType objectArg = this;
view raw gistfile1.cs hosted with ❤ by GitHub

defined and being passed into the local lambdas; and there is no re-use of these temporaries.

Fortunately, the code generated in a release build avoids creating one-shot variables for these intermediate input values (and optimizes away the call to ignore) -- but it still has a separate tuple value for each test, rather than re-using a single name. Chain enough calls together and the number of hidden variables will start to trigger metrics-based alarms.

It looks like F# code is going to be rather heavy going for tools to work with.

This is unchanged with the Feb 2010 CTP (1.9.9.9).

No comments :