Pattern Matching in .NET 4

| by Jonathan Allen 641 Followers on May 28, 2009. Estimated reading time: 4 minutes |

Case statements can be thought of as specialized version of the if/else syntax. There is nothing you couldn’t already do with them, and yet there are times when they seem much clearer. Consider this example in C# and VB.

double CaclRateByDate(DayOfWeek day)
{
if (day == DayOfWeek.Monday)
{
return .42;
}
else if (day == DayOfWeek.Tuesday)
{
return .67;
}
else if (day == DayOfWeek.Wednesday)
{
return .56;
}
else if (day == DayOfWeek.Thursday)
{
return .34;
}
else if (day == DayOfWeek.Friday)
{
return .78;
}
else if (day == DayOfWeek.Saturday)
{
return .92;
}
else if (day == DayOfWeek.Sunday)
{
return .18;
}
throw new ArgumentOutOfRangeException("Unexpected enum value");
}
Function CaclRateByDate(ByVal day As DayOfWeek) As Double
If day = Monday Then
Return 0.42
ElseIf day = Tuesday Then
Return 0.67
ElseIf day = Wednesday Then
Return 0.56
ElseIf day = Thursday Then
Return 0.34
ElseIf day = Friday Then
Return 0.78
ElseIf day = Saturday Then
Return 0.92
ElseIf day = Sunday Then
Return 0.18
Else
Throw New ArgumentOutOfRangeException("Unexpected enum value")
End If
End Function

Over and over again the developer is expected to write “ElseIf day =” or “else if (day ==” even though it doesn’t add any additional information. It is just noise that distracts from the important information, namely the day of the week and and the returned value.

In both VB and C#, we can thin this quite a bit by using a case statement.

double CaclRateByDate2(DayOfWeek day)
{
switch (day)
{
case DayOfWeek.Monday:
return .42;
case DayOfWeek.Tuesday:
return .67;
case DayOfWeek.Wednesday:
return .56;
case DayOfWeek.Thursday:
return .34;
case DayOfWeek.Friday:
return .78;
case DayOfWeek.Saturday:
return .92;
case DayOfWeek.Sunday:
return .18;
default:
throw new ArgumentOutOfRangeException("Unexpected enum value");
}
}
Function CalcRateByDate2(ByVal day As DayOfWeek) As Double
Select Case day
Case Monday
Return 0.42
Case Tuesday
Return 0.67
Case Wednesday
Return 0.56
Case Thursday
Return 0.34
Case Friday
Return 0.78
Case Saturday
Return 0.92
Case Sunday
Return 0.18
Case Else
Throw New ArgumentOutOfRangeException("Unexpected enum value")
End Select
End Function

But even with this, there is still a lot of code that just doesn’t need to be there. Why do you have to keep repeating the fact that you were returning a value? Wouldn’t it be nice if you could write something like this?

double CaclRateByDate2(DayOfWeek day)
{
return switch (day)
{
DayOfWeek.Monday: .42;
DayOfWeek.Tuesday: .67;
DayOfWeek.Wednesday: .56;
DayOfWeek.Thursday: .34;
DayOfWeek.Friday: .78;
DayOfWeek.Saturday: .92;
DayOfWeek.Sunday: .18;
default:
throw new ArgumentOutOfRangeException("Unexpected enum value");
}
}
Function CalcRateByDate2(ByVal day As DayOfWeek) As Double
Return Select Case day
Monday: 0.42
Tuesday: 0.67
Wednesday: 0.56
Thursday: 0.34
Friday: 0.78
Saturday: 0.92
Sunday: 0.18
Case Else
Throw New ArgumentOutOfRangeException("Unexpected enum value")
End Select
End Function

When you eliminate the stuff you don’t actually care about, C# and VB look remarkably similar. All that is left is the pattern you looking for and the result that you want when that pattern is found. This is what is known as Pattern Matching.

Well unfortunately we aren’t going to see something like this in C# 4 or VB 10. But there is a new language on the block that has support for pattern matching. Consider this F# example from Mathew Podwysocki. (Please note that in all these examples a function is being created.)

let calcRateByDay2 (day:System.DayOfWeek) =
match day with
| System.DayOfWeek.Monday -> 0.42
| System.DayOfWeek.Tuesday -> 0.67
| System.DayOfWeek.Wednesday -> 0.56
| System.DayOfWeek.Thursday -> 0.34
| System.DayOfWeek.Friday -> 0.78
| System.DayOfWeek.Saturday -> 0.92
| System.DayOfWeek.Sunday -> 0.18
| _ -> failwith "Unexpected enum value"

In another example, Mathew shows how to multiple parameters can be checked at the same time. In this example, the underscore is used as a wild card.

let allowUrl url port =
match (url, port) with
| "http://www.microsoft.com/", 80 -> true
| "http://example.com/", 8888 -> true
| _, 80 -> true
| _ -> false

Unfortunately F# doesn’t have a lock on concise syntax. If you need to do anything interesting with a value then you have to go back to specifying it by name or by a placeholder.

let calcRateByDay3 (day:System.DayOfWeek) =
match day with
| x when x >= System.DayOfWeek.Monday && x <= System.DayOfWeek.Friday -> 0.42
| System.DayOfWeek.Saturday -> 0.92
| System.DayOfWeek.Sunday -> 0.18
| _ -> failwith "Unexpected enum value"

let calcRateByDay3 (day:System.DayOfWeek) =
match day with
| _ when day >= System.DayOfWeek.Monday && day <= System.DayOfWeek.Friday -> 0.42
| System.DayOfWeek.Saturday -> 0.92
| System.DayOfWeek.Sunday -> 0.18
| _ -> failwith "Unexpected enum value"

By comparison, here is Visual Basic’s syntax for ranges within a case statement.

Function CaclRateByDate3(ByVal day As DayOfWeek) As Double
Select Case day
Case Monday To Friday : Return 0.42
Case Saturday : Return 0.92
Case Sunday : Return 0.18
Case Else
Throw New ArgumentOutOfRangeException("Unexpected enum value")
End Select
End Function

As you can see, each language in on the .NET platform has certain syntactical strengths that could be applied to the other languages without changing the language’s core nature.

Style

Hello stranger!

You need to Register an InfoQ account or or login to post comments. But there's so much more behind being registered.

Get the most out of the InfoQ experience.

Tell us what you think

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Email me replies to any of my messages in this thread
Close

by

on

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Email me replies to any of my messages in this thread

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Email me replies to any of my messages in this thread

Discuss