2
\$\begingroup\$

I recently read a really interesting post on adding functional pattern matching to C#. Here's a quick and dirty implementation using delegates and dictionaries.

Right now, I require that Match return an object of type TReturn. Is there any way I can easily extend my current implementation to be void (i.e. Match is not required to return anything) without significant code duplication? When I first tried this, I ended up essentially copying Match but omitting the second type parameter.

/// <summary>
/// Represents a switch on TIn. 
/// </summary>
public class Match<TIn, TReturn>
{
    private Dictionary<Type, Delegate> funcs =
        new Dictionary<Type, Delegate>();

    private Dictionary<TIn, TReturn> values =
        new Dictionary<TIn, TReturn>();

    private TReturn _else;

    /// <summary>
    /// Adds a case, action pair
    /// </summary>
    public void Case<T>(Func<T, TReturn> func)
        where T : TIn
    {
        funcs[typeof(T)] = func;
    }

    /// <summary>
    /// Adds a case, value pair
    /// </summary>
    public void Case(TIn x, TReturn value)
    {
        values[x] = value;
    }

    /// <summary>
    /// Adds the default case value
    /// </summary>
    public void Else(TIn x, TReturn value)
    {
        _else = value;
    }

    /// <summary>
    /// Evaluates the match on a query
    /// Returns an object of type TReturn 
    /// for first successful match
    /// </summary>
    public TReturn Eval<T>(T query) 
        where T : TIn 
    {
        TReturn value;
        if (values.TryGetValue(query, out value))
            return value;

        Delegate func;
        if (funcs.TryGetValue(query.GetType(), out func))
            return (TReturn)func.DynamicInvoke(query);

        return _else;
    }
}

And here's a simple test. One thing I was wondering: is it inefficient to declare a new Match inside the Calculate method?

class Program
{
    public static double Calculate(Expression exp)
    {
        var match = new Match<Expression, double>();
        match.Case<Num>((x) => x.Value);
        match.Case<Mul>((x) => Calculate(x.Left) * Calculate(x.Right));
        match.Case<Add>((x) => Calculate(x.Left) + Calculate(x.Right));
        return match.Eval(exp);
    }

    public static void Main(string[] args)
    {
        Expression tree = new Add(new Add(new Num(1), new Num(2)), new Mul(new Num(3), new Num(4)));
        double x = Calculate(tree) // = 15
    }
}

abstract class Expression
{
    public double Value;
}

abstract class BinaryExpression : Expression
{
    public Expression Left;
    public Expression Right;

    public BinaryExpression(Expression left, Expression right)
    {
        Left = left; Right = right;
    }
}

class Add : BinaryExpression
{
    public Add(Expression left, Expression right)
        : base(left, right)
    {
    }
}

class Mul : BinaryExpression
{
    public Mul(Expression left, Expression right)
        : base(left, right)
    {
    }
}

class Num : Expression
{
    public Num(double x)
    {
        Value = x;
    }
}
\$\endgroup\$
2
  • 1
    \$\begingroup\$ Warning, shameless self promotion: If you are interested in examining other existing pattern matching solutions, you may wish to read up on github.com/DavidArno/SuccincT/wiki/PatternMatching. \$\endgroup\$
    – David Arno
    Oct 1, 2015 at 8:18
  • \$\begingroup\$ There's a small bug in the definition of Else; it should only take a TReturn, not a TIn. My bad! \$\endgroup\$
    – rookie
    Oct 1, 2015 at 17:21

1 Answer 1

1
\$\begingroup\$

In your Match class, I would mark the two private dictionaries as readonly to signify intent and keep them from being assigned to accidentally anywhere else:

private readonly Dictionary<Type, Delegate> funcs =
    new Dictionary<Type, Delegate>();

private readonly Dictionary<TIn, TReturn> values =
    new Dictionary<TIn, TReturn>();

Secondly, I'd design to interfaces and make them IDictionaries as good OO practice:

private readonly IDictionary<Type, Delegate> funcs =
    new Dictionary<Type, Delegate>();

private readonly IDictionary<TIn, TReturn> values =
    new Dictionary<TIn, TReturn>();

In your example classes, you should forego public member data and use properties accessing backing fields (also marked readonly):

abstract class Expression
{
    private readonly double _Value;

    protected Expression(double value)
    {
        this._Value = value;
    }

    public double Value
    {
        get
        {
            return this._Value;
        }
    }
}

abstract class BinaryExpression : Expression
{
    private readonly Expression _Left;

    private readonly Expression _Right;

    public BinaryExpression(Expression left, Expression right) : base(default(double))
    {
        this._Left = left;
        this._Right = right;
    }

    public Expression Left
    {
        get
        {
            return this._Left;
        }
    }

    public Expression Right
    {
        get
        {
            return this._Right;
        }
    }
}

class Add : BinaryExpression
{
    public Add(Expression left, Expression right) : base(left, right)
    {
    }
}

class Mul : BinaryExpression
{
    public Mul(Expression left, Expression right) : base(left, right)
    {
    }
}

class Num : Expression
{
    public Num(double x) : base(x)
    {
    }
}
\$\endgroup\$
1
  • 1
    \$\begingroup\$ I usually make my fields public readonly. Thanks for the suggestions! \$\endgroup\$
    – rookie
    Sep 30, 2015 at 21:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.