Friday, January 20, 2012

Translating If-Then-Else Control Flow Idiom to F#

I was reading through Juval Löwy's Programming WCF Services book and wondering if I should do a series of WCF blog posts in F# based on Löwy's book when I ran into a common construct found in C# programs. That construct looks something like the following C# code:

    public static void MyMethod(String oldstuff, String newstuff, bool flag)
    {
        if (oldstuff == null)
         throw new Exception("oldstuff is null!");

        if (newstuff == null)  {
            DoSomething("Default");
            return;
        }
        if (flag == false)  {
            DoSomething(oldstuff);
            return;
        }
        DoSomething(newstuff);
    }

This is a construct that I oftened have used in the past and have never thought about it much. But when you translate the above code directly into F#, it becomes a lot more verbose because F# requires you to implement the then clause. A direct translation to F# as follows:

let mymethod oldstuff newstuff flag =
    if oldstuff = null then 
        raise (new Exception("oldstuff is null!"))
    else
        if newstuff = null then
            DoSomething("Default")
        else
            if flag = false then
                DoSomething(oldstuff)
            else
                DoSomething(newstuff)

If I had a lot of these if-then-else statements in my C# method, then my F# version would disappear off the screen to the right if I tried to implement it by direct translation. I thought about how I could implement this in F# and came up with this following possibility:

let revised_mymethod oldstuff newstuff flag =
    let (_,action) =
        [(oldstuff=null,  lazy (raise (new Exception("oldstuff is null!"))));
         (newstuff=null,  lazy (DoSomething("Default")));
         (flag=false,     lazy (DoSomething(oldstuff)));
         (flag=true,      lazy (DoSomething(newstuff)))]
        |> List.filter fst
        |> List.head
    action.Force()

Rewriting the C# code in this fashion makes me think of rules engines and after refactoring out some common code, I could rewrite the above F# code as follows:

let followrules (xs:(bool*Lazy<unit>) list) =
    (xs |> List.filter fst |> List.head |> snd).Force()

let revised_mymethod2 oldstuff newstuff flag =
    [(oldstuff=null , lazy (raise (new Exception("oldstuff is null!"))));
     (newstuff=null,  lazy (DoSomething("Default")));
     (flag=false,     lazy (DoSomething(oldstuff)));
     (flag=true,      lazy (DoSomething(newstuff)))]
    |> followrules

With this new construct, I can easily re-arrange the order of evaluation, add or remove new conditions. This new construct just seems to have more advantages than the old if-then-else construct in F#.

4 comments:

ptasz3k said...

Why don't You use elif instead of else .. if?

John Liao said...

You're right, in this poorly constructed example, I could have used elif instead of else .. if. I guess in my mind, I was thinking of more if (flag) { DoSomething(); } without the return and not exiting out of the method right away and dropping into the rest of the method execution blocks.

snarky anonymus said...

1. It's poor style to compare booleans with constants. Or you have another opinion?
2. Do not use 'lazy' before you understand it. Use fun () -> ...
3. List.filter followed by List.head is List.find.
4. Learn point-free. Shorten your code.

John Liao said...

Thanks to the feedback from snarky. I should have used List.find instead of List.filter followed by List.head. I suppose that I could have used anonymous functions vs lazy eval for the actions part and maybe it is not idiomatic F# to use it this way, but are there other drawbacks to it? I not sure what you mean by point-free, are you referring to the fact that I should implement followrules like the following:

let followrules (xs:(bool*(unit->unit)) list) = snd (List.find fst xs)()

and have the calling code become:
(flag, fun () -> DoSomething())

I'm not so sure what you are referring to in comparing booleans with constants. I need a boolean result passed to followrules. If you are referring to (flag=true) and (flag=false); in that situation, I could flip the order and the results would be different.