{"action":"created","issue":{"url":"https://api.github.com/repos/dotnet/roslyn/issues/5016","labels_url":"https://api.github.com/repos/dotnet/roslyn/issues/5016/labels{/name}","comments_url":"https://api.github.com/repos/dotnet/roslyn/issues/5016/comments","events_url":"https://api.github.com/repos/dotnet/roslyn/issues/5016/events","html_url":"https://github.com/dotnet/roslyn/issues/5016","id":104923071,"number":5016,"title":"[Proposal]: match construct for pattern matching","user":{"login":"HaloFour","id":10102290,"avatar_url":"https://avatars.githubusercontent.com/u/10102290?v=3","gravatar_id":"","url":"https://api.github.com/users/HaloFour","html_url":"https://github.com/HaloFour","followers_url":"https://api.github.com/users/HaloFour/followers","following_url":"https://api.github.com/users/HaloFour/following{/other_user}","gists_url":"https://api.github.com/users/HaloFour/gists{/gist_id}","starred_url":"https://api.github.com/users/HaloFour/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/HaloFour/subscriptions","organizations_url":"https://api.github.com/users/HaloFour/orgs","repos_url":"https://api.github.com/users/HaloFour/repos","events_url":"https://api.github.com/users/HaloFour/events{/privacy}","received_events_url":"https://api.github.com/users/HaloFour/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":6,"created_at":"2015-09-04T16:25:20Z","updated_at":"2015-09-05T16:16:03Z","closed_at":null,"body":"It's been mentioned several times in the different pattern matching threads (#206, #2136, #4781, #4944) so I thought I'd at least get the ball rolling with a proposal to introduce a `match` construct which would be specifically suited for pattern matching.  I more or less yoinked this syntax out of a hat and I am not married to it so feel free to suggest alternatives.  I chose to follow the lambda syntax fairly closely as I think that most C# developers should be fairly familiar with the rules of writing lambda expressions and anonymous function bodies so the behavior of encountering a `return` within the body should not be confusing.\r\n\r\n### Syntax:\r\n\r\nThe syntax would be as follows:\r\n\r\n> match-statement:\r\n> &nbsp;&nbsp;&nbsp;&nbsp;match ( `expression` ) `match-block`\r\n>\r\n> match-block:\r\n> &nbsp;&nbsp;&nbsp;&nbsp;`match-sections`_<sub>opt</sub>_\r\n>\r\n> match-sections:\r\n> &nbsp;&nbsp;&nbsp;&nbsp;`match-section`\r\n> &nbsp;&nbsp;&nbsp;&nbsp;`match-sections`, `match-section`\r\n>\r\n> match-section:\r\n> &nbsp;&nbsp;&nbsp;&nbsp;`pattern` `guard-block`<sub>opt</sub> => `anonymous-function-body`\r\n>\r\n> guard-block:\r\n> &nbsp;&nbsp;&nbsp;&nbsp;when ( `expr` )\r\n\r\nI've attempted to adopt this grammar from the `switch` syntax in §8.7.2 of the C# 5.0 specification, feel free to correct.\r\n\r\nThe `match` statement can be used as both a statement and an expression.  In the case of being used as an expression the inferred result type of each of the `anonymous-function-body`s within each `match-section` must be inferred to the same type which is the result type of the `match` expression itself.\r\n\r\nThe `match` statement must be complete meaning that all possible patterns must be checked otherwise it will result in a compile-time error.\r\n\r\nDifferences between `switch` and `match` in statement form:\r\n\r\n* Identifier scope is limited to within each `match-section`.  This applies to both identifiers use with `var` patterns as well as variables defined within the `anonymous-function-body`.\r\n* Each `match-section` is self-contained and there is no automatic fall-through.  In short, it is not necessary to `break` out of a section.\r\n* It is not possible to return out of the method enclosing the `match` statement.  The `return` statement instead acts as a way to break out of the current `match-section`.\r\n\r\n### Examples:\r\n\r\n##### `match` as an expression:\r\n\r\nWhen using `match` in an expression the result type of each of the expressions or anonymous function bodies must be inferred to the same type.  That type cannot be `void`.\r\n\r\n```cs\r\nvar area = match (shape) {\r\n    Point(*, *) => 0,\r\n    Line(Point(*, *), Point(*, *)) => 0,\r\n    Square(var width) => Rectangle(width, width),\r\n    Rectangle(var width, var height) => width * height,\r\n    Circle(var radius) => {\r\n        double pi = Math.PI;\r\n        return (radius * radius * pi);\r\n    };\r\n    * => throw new InvalidOperationException(\"Unrecognized shape!\");\r\n};\r\n```\r\n\r\n##### `match` as a statement:\r\n\r\nWhen using `match` in a statement the result type of each of the expressions or anonymous function bodies must be inferred to return `void`.\r\n\r\n```cs\r\nmatch (shape) {\r\n    Point(var x, var y) => Console.WriteLine($\"Shape is a point at ({x}, {y}).\"),\r\n    Line(Point(var x, var y), Point(var x2, var y2)) =>\r\n        Console.WriteLine($\"Shape is a line from ({x}, {y}) to ({x2}, {y2}).\"),\r\n    Square(var width) =>\r\n        Console.WriteLine($\"Shape is a square where each side is {width}.\"),\r\n    Rectangle(var width, var height) =>\r\n        Console.WriteLine($\"Shape is a rectangle of {width} by {height}.\"),\r\n    Circle(var radius) => {\r\n        if (radius <= 0) {\r\n            return; // breaks out of the match expression\r\n        }\r\n        Console.WriteLine($\"Shape is a circle with a radius of {radius}.\")\r\n    },\r\n    Hexagon(*) => throw new InvalidOperationException(\"I don't like hexagons.\"),\r\n    * => Console.WriteLine(\"I don't know what kind of shape this is!\")\r\n};\r\n```\r\n\r\nIn the case of a simple `expression` the inferred result type can be anything but will be discarded, similarly to how you can assign a lambda that calls a function that returns a value to an `Action` delegate.\r\n\r\n```cs\r\nStringBuilder builder = ...;\r\n\r\n // legal, the return type of AppendLine is discarded\r\nAction action = () => builder.AppendLine(\"foo\");\r\n\r\nmatch (shape) {\r\n    // legal, the return type of AppendLine is discarded \r\n    Point(var x, var y) => builder.AppendLine($\"Point ({x}, {y})\"),\r\n\r\n    // not legal, the body cannot explicitly return a value\r\n    Line(Point(var x, var y), Point(var x2, var y2)) => {\r\n        return builder.AppendLine(\"\");\r\n    },\r\n    * => { } // do nothing, for completeness\r\n};\r\n```\r\n\r\nThis is the equivalent `switch` statement:\r\n\r\n```cs\r\nswitch (shape) {\r\n    case Point(var x, var y):\r\n        Console.WriteLine($\"Shape is a point at ({x}, {y}).\");\r\n        break; // must explicitly deal with fall-through\r\n    case Line(Point(var x1, var y1), Point(var x2, var y2)): // cannot reuse identifiers\r\n        Console.WriteLine($\"Shape is a line from ({x}, {y}) to ({x2}, {y2}).\");\r\n        break;\r\n    case Square(var width):\r\n        Console.WriteLine($\"Shape is a square where each side is {width}.\");\r\n        break;\r\n    case Rectangle(var width2, var height2):\r\n        Console.WriteLine($\"Shape is a rectangle of {width} by {height}.\");\r\n        break;\r\n    case Circle(var radius):\r\n        if (radius <= 0) {\r\n            break;\r\n        }\r\n        Console.WriteLine($\"Shape is a circle with a radius of {radius}.\");\r\n        break;\r\n    case Hexagon(*):\r\n        throw new InvalidOperationException(\"I don't like hexagons.\");\r\n    default:\r\n        Console.WriteLine(\"I don't know what kind of shape this is!\");\r\n        break;\r\n};\r\n```\r\n"},"comment":{"url":"https://api.github.com/repos/dotnet/roslyn/issues/comments/137972088","html_url":"https://github.com/dotnet/roslyn/issues/5016#issuecomment-137972088","issue_url":"https://api.github.com/repos/dotnet/roslyn/issues/5016","id":137972088,"user":{"login":"HaloFour","id":10102290,"avatar_url":"https://avatars.githubusercontent.com/u/10102290?v=3","gravatar_id":"","url":"https://api.github.com/users/HaloFour","html_url":"https://github.com/HaloFour","followers_url":"https://api.github.com/users/HaloFour/followers","following_url":"https://api.github.com/users/HaloFour/following{/other_user}","gists_url":"https://api.github.com/users/HaloFour/gists{/gist_id}","starred_url":"https://api.github.com/users/HaloFour/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/HaloFour/subscriptions","organizations_url":"https://api.github.com/users/HaloFour/orgs","repos_url":"https://api.github.com/users/HaloFour/repos","events_url":"https://api.github.com/users/HaloFour/events{/privacy}","received_events_url":"https://api.github.com/users/HaloFour/received_events","type":"User","site_admin":false},"created_at":"2015-09-05T16:16:03Z","updated_at":"2015-09-05T16:16:03Z","body":"@AdamSpeight2008 Ooo, I forgot guard blocks.\r\n\r\nI'm honestly not a fan of the `|:` syntax.  It just feels out of place to me in C# and VB.NET (not that I've approached how that syntax would look).  Feels like something out of Scala but even that language keeps its pattern matching syntax pretty basic by just using `case`.  Your inspiration is Nemerle, right?  I'm not very familiar with that language but they seem to treat `|` in pattern matching as an \"or\" operation, as they do with enums.  I see they also terminate sections with semi-colon similarly to your examples."}}