Sunday, March 13, 2016

C# equivalent to F# Active Patterns

active patterns in F# code F# has an interesting feature called Active Patterns. I liked the idea and started thinking how I would implement this in C#. It all started from this StackOverflow question to which only Scala answers were given at the time.

Yeah, if you read the Microsoft definition you can almost see the egghead that wrote that so that you can't understand anything. Let's start with a simple example that I have shamelessly stolen from here.
// create an active pattern

let (|Int|_|) str = 
    match System.Int32.TryParse(str) with
    | (true, int) -> Some(int)
    | _ -> None

// create an active pattern

let (|Bool|_|) str = 
    match System.Boolean.TryParse(str) with
    | (true, bool) -> Some(bool)
    | _ -> None

// create a function to call the patterns

let testParse str = 
    match str with
    | Int i -> printfn "The value is an int '%i'" i
    | Bool b -> printfn "The value is a bool '%b'" b
    | _ -> printfn "The value '%s' is something else" str

// test

testParse "12"
testParse "true"
testParse "abc"

The point here is that you have two functions that return a parsed value, either int or bool, and also a matching success thing. That's a problem in C#, because it is strongly typed and if you want to use anything than boxed values in objects, you need to define some sort of class that holds two values. I've done that with a class I called Option<T>. You might want to see the code, but it is basically a kind of Nullable class that accepts any type, not just value types.
Click to expand

Then I wrote code that did what the original code did and it looks like this:
var apInt = new Func<string, Option<int>>(s =>
{
    int i;
    if (System.Int32.TryParse(s, out i)) return new Option<int>(i);
    return Option<int>.Empty;
});
var apBool = new Func<string, Option<bool>>(s =>
{
    bool b;
    if (System.Boolean.TryParse(s, out b)) return new Option<bool>(b);
    return Option<bool>.Empty;
});

var testParse = new Action<string>(s =>
    {
        var oi = apInt(s);
        if (oi.HoldsValue)
        {
            Console.WriteLine($"The value is an int '{oi.Value}'");
            return;
        }
        var ob = apBool(s);
        if (ob.HoldsValue)
        {
            Console.WriteLine($"The value is an bool '{ob.Value}'");
            return;
        }
        Console.WriteLine($"The value '{s}' is something else");
    });

testParse("12");
testParse("true");
testParse("abc");

It's pretty straighforward, but I didn't like the verbosity, so I decided to write it in a fluent way. Using another class called FluidFunc that I created for this purpose, the code now looks like this:
var apInt = Option<int>.From<string>(s =>
{
    int i;
    return System.Int32.TryParse(s, out i) 
        ? new Option<int>(i) 
        : Option<int>.Empty;
});

var apBool = Option<bool>.From<string>(s =>
{
    bool b;
    return System.Boolean.TryParse(s, out b)
        ? new Option<bool>(b)
        : Option<bool>.Empty;
});

var testParse = new Action<string>(s =>
{
    FluidFunc
        .Match(s)
        .With(apInt, r => Console.WriteLine($"The value is an int '{r}'"))
        .With(apBool, r => Console.WriteLine($"The value is an bool '{r}'"))
        .Else(v => Console.WriteLine($"The value '{v}' is something else"));
});

testParse("12");
testParse("true");
testParse("abc");

Alternately, one might use a Tuple<bool,T> to avoid using the Option class, and the code might look like this:
var apInt = FluidFunc.From<string,int>(s =>
{
    int i;
    return System.Int32.TryParse(s, out i)
        ? new Tuple<bool, int>(true, i)
        : new Tuple<bool, int>(false, 0);
});

var apBool = FluidFunc.From<string,bool>(s =>
{
    bool b;
    return System.Boolean.TryParse(s, out b)
        ? new Tuple<bool, bool>(true, b)
        : new Tuple<bool, bool>(false, false);
});

var testParse = new Action<string>(s =>
{
    FluidFunc
        .Match(s)
        .With(apInt, r => Console.WriteLine($"The value is an int '{r}'"))
        .With(apBool, r => Console.WriteLine($"The value is an bool '{r}'"))
        .Else(v => Console.WriteLine($"The value '{v}' is something else"));
});

testParse("12");
testParse("true");
testParse("abc");

As you can see, the code now looks almost as verbose as the original F# code. I do not pretend that this is the best way of doing it, but this is what I would do. It also kind of reminds me of the classical situation when you want to do a switch, but with dynamic calculated values or with complex object values, like doing something based on the type of a parameter, or on the result of a more complicated condition. I find this fluent format to be quite useful.

One crazy cool idea is to create a sort of Linq provider for regular expressions, creating the same type of fluidity in generating regular expressions, but in the end getting a ... err... regular compiled regular expression. But that is for other, more epic posts.

The demo solution for this is now hosted on Github.

Here is the code of the FluidFunc class, in case you were wondering:
public static class FluidFunc
{
    public static FluidFunc<TInput> Match<TInput>(TInput value)
    {
        return FluidFunc<TInput>.With(value);
    }

    public static Func<TInput, Tuple<bool, TResult>> From<TInput, TResult>(Func<TInput, Tuple<bool, TResult>> func)
    {
        return func;
    }
}

public class FluidFunc<TInput>
{
    private TInput _value;
    private static FluidFunc<TInput> _noOp;
    private bool _isNoop;

    public static FluidFunc<TInput> NoOp
    {
        get
        {
            if (_noOp == null) _noOp = new FluidFunc<TInput>();
            return _noOp;
        }
    }

    private FluidFunc()
    {
        this._isNoop = true;
    }

    private FluidFunc(TInput value)
    {
        this._value = value;
    }

    public static FluidFunc<TInput> With(TInput value)
    {
        return new FluidFunc<TInput>(value);
    }

    public FluidFunc<TInput> With<TNew>(Func<TInput, Option<TNew>> func, Action<TNew> action)
    {
        if (this._isNoop)
        {
            return this;
        }
        var result = func(_value);
        if (result.HoldsValue)
        {
            action(result.Value);
            return FluidFunc<TInput>.NoOp;
        }
        return new FluidFunc<TInput>(_value);
    }

    public FluidFunc<TInput> With<TNew>(Func<TInput, Tuple<bool,TNew>> func, Action<TNew> action)
    {
        if (this._isNoop)
        {
            return this;
        }
        var result = func(_value);
        if (result.Item1)
        {
            action(result.Item2);
            return FluidFunc<TInput>.NoOp;
        }
        return new FluidFunc<TInput>(_value);
    }

    public void Else(Action<TInput> action)
    {
        if (this._isNoop) return;

        action(_value);
    }

}

0 comments: