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;
}
}
Else
; it should only take aTReturn
, not aTIn
. My bad! – rookie Oct 1 '15 at 17:21