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) | |
&& ... |
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)) |
Then we can write the chained tests as
arg1 >+?? arg2 | |
>?> Test1 | |
>?> (Test2 argA) | |
>?> Test3 | |
... | |
|> ignore |
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. |
If the Test
function being used is a member function of the class, you get temporaries like
MyType objectArg = this; |
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 :
Post a Comment