VB LDM 2014-04-23 - pattern-matching and primary constructors

Topics: C# Language Design, VB Language Design
Coordinator
Apr 28, 2014 at 6:21 AM
Edited Apr 28, 2014 at 6:50 PM
(this discussion happened in VB language design meeting, but it strongly concerns C# also).

Better support for immutable data, with pattern-matching

Request

Please make it easier for me to declare immutable data-types, and make it easier to code over them via pattern-matching. Easier immutable datatypes will make my code have fewer defects, and pattern-matching over immutable data (like in F#, Scala and other functional languages) will allow clean readable maintanable coding idioms.

For instance, I have a method and want to return 2-3 return values. Here's the ugly code I write now:
// Ugly 1: use out params (but doesn't work with async)
void Lookup(out string name, out int x, out int y) {...}
...
Lookup(out name, out x, out y); Console.WriteLine(name);


// Ugly 2: use a tuple (but my consumer has a hard time knowing which element is what)
Tuple<string,int,int> Lookup() {...}
...
var r = Lookup(); Console.WriteLine(r.Item1);


// Ugly 3: declare a custom datatype
class LookupResult(string name, int x, int y)
{
   public string Name {get;} = name;
   public int X {get;} = x;
   public int Y {get;} = y;
}
LookupResult Lookup() {...}
...
var r = Lookup(); Console.WriteLine(r.Name);
What I'd like is to write all this much more simply:
class LookupResult(string name, int x, int y);
LookupResult Lookup() {...}

var r = Lookup(); Console.WriteLine(r?.name);
Even better, I'd like to deconstruct the returned value using pattern-matching:
if (Lookup() matches LookupResult(var name, var x, var y)) { ... }

Background

We have been guided in our thoughts by the paper "Matching Objects with Patterns" by Emir, Odersky and Williams. There's a link to it on this Scala webpage: http://www.scala-lang.org/old/node/112

The Roslyn compiler currently has "primary constructors" in C#, and a form of "typecase" in VB:
class C(int x, int y)
{
    public int X { get; set; } = x;
    public int Y { get; set; } = y;
}
Function GetArea(shape As Shape) As Double
    Select Case shape
        Case r As Rect : Return r.X * r.Y
        Case c As Circle : Return Math.PI * c.Radius * c.Radius
    End Select
End Function

' Once we add Out expressions to VB, you can get more pattern-matchey
Function Eval(expr As Expr) As Integer
    Select Case expr
        Case e As PlusExpr When e.Matches(0, Out rhs)
            Return Eval(rhs)
        Case e As PlusExpr When e.Matches(Out rhs, 0)
            Return Eval(rhs)
        Case e As PlusExpr When e.Matches(Out lhs, Out rhs)
            Return Eval(lhs) + Eval(rhs)
        Case e As IntExpr
            Return e.Val
    End Select
End Function
Question: how will the two features work together? Will there be a "sweet spot" where their designs mesh together well, and mesh with additional features in future versions of the languages, to produce a nice pattern-matching design? Something as nice as what F# or Scala have?

Urgent question: do we need to make changes to the CURRENT design of primary constructors and Select Case so that we don't block off a future world of pattern-matching?

PROPOSAL: Records, and Plain Old CLR Objects

Here is a coherent set of proposals that combine construction and deconstruction into one neat easily-written proposal, to support the scenario in the "Request".


Imagine something called a "Record", a type with an ordered list of named data members. Not saying there should be a new kind of type called a "record type" alongside classes and structs... indeed it might be best not to have a new kind of type, since we might want record-like classes and structs.

PROPOSAL 1: a record can be defined using primary-constructor syntax, and it is syntactic sugar for an expanded form as follows...
class Point(int X, int Y);

==>

class Point(int X, int Y)
{
   public int X { get; } = X;
   public int Y { get; } = Y;
}
The rule is: "When you write a record, it automatically generates properties for the PRIMARY PROPERTIES, unless you have provided those properties yourself". The term "Primary Properties" refers to the parameters in the primary-constructor syntax. Thus, if you didn't want it to auto-generate a property, you'd have to provide your own version of that property, as in the example below. (There's no way to say that you don't want the property with this syntax: if you don't want the property, then don't use the feature at all).
class Point(int X, int Y) { public int X => 15; }

==>

class Point(int X, int Y)
{
   public int X => 15;
   public int Y {get; set;} = Y;
}
PROPOSAL 2: Just as we've seen the compiler auto-generate properties if you failed to provide them yourself, it will also auto-generate a default implementation of Equals and GetHashCode if you fail to provide them yourself. This default implementation will do element-wise calls to Equals and GetHashCode on the primary properties.

(By default, Proposal 1 uses immutable properties, and so Equals/GetHashCode will terminate. It's bad practice to for GetHashCode to calculate based on mutable properties.)


PROPOSAL 3: In addition to auto-generated properties, Equals, GetHashCode, we also auto-generate a MAtch operator (unless one is provided by the user)...
class Point(int X, int Y)
{
  public int X {get;} = X;
  public int Y {get;} = Y;
  public static bool operator Matches(Point self, out int X, out int Y) {X=self.X; Y=self.Y; return true;}
}
In addition to this, we introduce a new MATCH operator into the language:
<expr> matches T(args)
Operational semantics:
  1. Evaluate "var tmp1 = <expr>"
  2. Look in type "T" for an overload of "Matches" whose 2nd...last parameters match the arguments
  3. Evaluate "var tmp2 = tmp1 As TFirst" where TFirst is the type of the first parameter of that overload
  4. If this is null, then the match operator returns false
  5. Otherwise, the result invoke the Match operator, passing it (tmp2, args) as arguments.
You'd use the match operator like this:
if (f() matches T(var name, var x, var y)) { ... }
Note that this would have different rules from the way out vars are currently conceived. These out be definitely assigned only if the "e matches T(args)" returns true. Incidentally, note that the T's Match operator is not necessarily invoked... e.g. if f() returns null, then it won't be called!



PROPOSAL 4: You could also treat the typename as a method, e.g.
var p = Point(2,3);
==>
var p = new Point(2,3);
All this does is remove the need for the word "new" for these record types. It might seem pointless, but it unifies the way you write construction and deconstruction...
var p = Point(2,3);
if (p matches Point(out x, out y)) { ... }

Discussion

There were questions raised, heated discussions, and no agreed-upon answers.


Q. If we go with primary constructors as they're currently envisaged (where the primary parameters don't define fields, and instead only exist at construction-time), then will we still be able to support this vision of pattern-matching?

A. Not directly. But we could support this vision through a new modifier "record", e.g. record class Point(int x, int y)



Q. What is the difference between putting all this "record" stuff (auto-generated properties, Equals, GetHashCode, Match operator) into the existing primary constructor syntax, vs putting them in via a new modifier "record" ?

A1. It's a language-cleanliness problem. It would feel goofy for the primary constructor syntax to mean such different things depending on whether the "record" modifier is present.

A2. Case is different. The convention in C# will be to use lower-case for primary constructor parameters, since users will write out the properties manually and use upper-case for that. But with records, the convention will probably be to use uper-case for the primary properties.



Q. It feels wrong for primary-constructors to be so closely tied to immutable data and pattern-matching. Primary constructors have many other uses even when you don't want the rest, e.g. for coding visitor-patterns. Can we add all the features (primary constructors, Matches, Equals/GetHashCode, auto-generated properties) individually and orthogonally over time? And then use "concise declaration" (via whatever syntax) as the thread that ties everything together and delivers the smooth end-to-end use-case?

A. Maybe our goal should be to make a concise story for the desired use-case, rather than to add orthogonal features?


Q. Is it important that users can declare Match operators today, right from the first version of primary constructors? (granted, this would be pretty much useless on its own: the Match operator only becomes useful in conjunction with all the other features).



Q. How would these "record types" work with serialization? People commonly use Plain Old CLR Objects (POCO) when they want DataContract serialization. But there's no clear way with this proposal to attribute the individual properties.



Q. What's needed to ship in the first version of primary-constructors so as to allow this kind of evolution over time? Are there any conflicts with the current proposals (of primary-constructors and SelectCase) that would prevent such evolution?

A. Neal and Mads will meet, discuss this question, and take it to C# LDM next week.
Apr 30, 2014 at 5:34 AM
Any chance of something like
var res = match(data)
{
    matches T(var name, var x, var y)) => x + y
}
?

It would be great if that construction could match against types, lists, etc. I like the way it is implemented in Nemerle: https://github.com/rsdn/nemerle/wiki/Quick-guide#Pattern_Matching
May 2, 2014 at 2:57 AM
I mentioned some of this in the language notes thread; but I think it's worthwhile responding to everything in the proposals:

Proposal 1 & 2: I like these a lot. I think they make it just as easy to declare useful named types as it is to declare anonymous types currently. Having class Foo(int a); syntax in the language would mean its so trivially easy to make DTOs that the horrid Tuple class would get much less use. I think these types should be able to be declared in method scope as well, just like anonymous types can be. Compiler generated GetHashCode, Equals, and ToString would make these sorts of types incredibly useful. I think having a separate record keyword is unnecessary however.

3 & 4: With declaration expressions, this seems totally unnecessary. I can very easy write this myself:
public class Foo(public int A, public int B)
{
    public void Decompose(out int a, out int b)
    {
        a = A;
        b = B;
    }
}

var foo = new Foo(3, 4);
foo.Decompose(out var a, out var b);
Console.WriteLine(a);
Console.WriteLine(b);
Am I missing something?
Developer
May 2, 2014 at 4:04 AM
MgSam wrote:
3 & 4: With declaration expressions, this seems totally unnecessary. I can very easy write this myself:

Am I missing something?
Yes, among other things you've missed a null check and you've taken the code out of an expression context. In your formulation pattern matching does not compose. This note does not describe my full proposal, but the full proposal would allow you to write
var foo = Foo(3, 4);
if (foo matches Foo(3, 4)) { // code to execute if this is a Foo with a=3 and b=4.
object o = foo;
switch (o)
{
    case Foo(3, var b):
        // code to execute if o is a Foo with a=3, with b available as a variable
        break;
}
See http://lampwww.epfl.ch/~emir/written/MatchingObjectsWithPatterns-TR.pdf for some of the inspiration behind this feature.
May 2, 2014 at 10:55 AM
Edited May 2, 2014 at 10:56 AM
First of all, I really love the concept of pattern matching in C#. Some of my thoughts:

1) Concerning the record keyword
The rule is: "When you write a record, it automatically generates properties for the PRIMARY PROPERTIES, unless you have provided those properties yourself"
Why would you want to provide the properties yourself? Wouldn't it just be cleaner to only allow the syntax in the form: class Point(int X, int Y); ? It would eliminate the need for record keyword, as you could simply say that when class/struct has no body it is record (auto generated GetHashCode/Equals etc.). If you decide to allow those properties yourself, would they be used in GetHashCode/Equals auto generated methods? If so, what if someone decides to have them change each time they are used (for example returning random value from one of them). I think it would only allow subtle bugs and it's not really needed.

2) The _ parameter

In many languages which have pattern matching, there appears to be identifier which matches every value and can be repeated any number of times in pattern matching clause without capturing the variable. Maybe it should be introduced here as well?
  case Foo(3, _, _):
        // code to execute if o is a Foo with a=3, with 2nd and 3rd parameter of foo ignored
        break;
I think it looks a little out of place in C#, so I would suggest some other form like keyword any:
  case Foo(3, any, any):
        // code to execute if o is a Foo with a=3, with 2nd and 3rd parameter of foo ignored
        break;
I often wanted something similar in lambda expressions also:

item.Changed += (any, any) => ...
May 2, 2014 at 12:52 PM
First of all, the record feature should be implemented before Primary Constuctors are. The implementations of Primary Constructors has been debated by many people, and the team themselves have changed their mind about how to implement them. Primary Constructors should be delayed until something can really be figured out.

These record types (by which I mean just the basic Point(int X, int Y); with auto-implemented ToString, Equals/GetHashCode) should be implemented first. I've yet to hear any complaints about these, they are a feature that's much more useful.

Everyone you show that to goes "Oh that's awesome, when do we get it?". It should really be a priority for the team. (I'd be willing to look into it myself even).
Developer
May 2, 2014 at 5:26 PM
ghordynski wrote:
First of all, I really love the concept of pattern matching in C#. Some of my thoughts:

1) Concerning the record keyword
The rule is: "When you write a record, it automatically generates properties for the PRIMARY PROPERTIES, unless you have provided those properties yourself"
Why would you want to provide the properties yourself?
There are a number of reasons you might want to modify the default implementation. You might want to change its access level, or make it virtual or override.
2) The _ parameter

In many languages which have pattern matching, there appears to be identifier which matches every value and can be repeated any number of times in pattern matching clause without capturing the variable. Maybe it should be introduced here as well?
That is part of the full proposal (that you haven't seen). However, the syntax I proposed for a "wildcard" pattern is *
May 2, 2014 at 7:37 PM
mirhagk wrote:
These record types (by which I mean just the basic Point(int X, int Y); with auto-implemented ToString, Equals/GetHashCode) should be implemented first. I've yet to hear any complaints about these, they are a feature that's much more useful.
The features have different kind of usefulness. Handy record declarations would reduce the amount of boilerplate code in many situations, but would not allow a programmer to do anything which couldn't already be done with a utility that took a list of members and auto-generated a boilerplate type definition. Primary constructors, if properly implemented, would make it possible to write classes which do things that are at present simply not possible in C#--at least not without horrible kludges involving ThreadStatic variables.

That having been said, I think a substantial argument in favor of pushing handy aggregates ahead of primary constructors is that anyone whose needs aren't fulfilled by any aspect of a handy record declaration could simply implement types the old fashioned way unless or until a future version of C# adds options to better fit their needs. By contrast, if primary constructors lack a feature that someone needs, it's much less likely that there would be a workaround. Further, it may not be possible for future versions of C# to correct the deficiencies without breaking by-then-existing code.
May 15, 2014 at 6:53 PM
Edited May 15, 2014 at 7:00 PM
@C-SharpTeam Has there been any further discussion over any of the features discussed here? The proposed record types and syntax are easily the number 1 most useful thing that has been proposed for C# 6.0, at least in my workflow. More so than even primary constructors.

For example, today I just wrote a class like this:
    /// <summary>
    /// A key for a metric.
    /// </summary>
    public struct MetricKey
    {
        /// <summary>
        /// The symbol.
        /// </summary>
        public String Symbol { get; private set; }
        /// <summary>
        /// The date of the metric.
        /// </summary>
        public DateTime Date { get; private set; }

        /// <summary>
        /// Creates a metric.
        /// </summary>
        /// <param name="symbol">Symbol.</param>
        /// <param name="date">The date.</param>
        public MetricKey(String symbol, DateTime date) : this()
        {            
            Symbol = symbol;
            Date = date;
        }

        /// <summary>
        /// Compares to another MetricKey for equality.
        /// </summary>
        /// <param name="other">The other MetricKey to compare to.</param>
        /// <returns>Whether the two MetricKeys were equivalent.</returns>
        public bool Equals(MetricKey other)
        {
            return string.Equals(Symbol, other.Symbol) && Date.Equals(other.Date);
        }

        /// <summary>
        /// Indicates whether this instance and a specified object are equal.
        /// </summary>
        /// <returns>
        /// true if <paramref name="obj"/> and this instance are the same type and represent the same value; otherwise, false.
        /// </returns>
        /// <param name="obj">Another object to compare to. </param><filterpriority>2</filterpriority>
        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj)) return false;
            return obj is MetricKey && Equals((MetricKey) obj);
        }

        /// <summary>
        /// Returns the hash code for this instance.
        /// </summary>
        /// <returns>
        /// A 32-bit signed integer that is the hash code for this instance.
        /// </returns>
        /// <filterpriority>2</filterpriority>
        public override int GetHashCode()
        {
            unchecked
            {
                return ((Symbol != null ? Symbol.GetHashCode() : 0) * 397) ^ Date.GetHashCode();
            }
        }
    }
The entire purpose of this class is to allow using a composite key with meaningful property names in a Dictionary. It would be amazing if I could write this whole thing as:
struct MetricKey(String Symbol, String Date);
That would be an enormous win.

@nmgafter Thanks for the link. I understand the feature better now, though I still am not sure if its worth the cost of the added complexity to the language. A really compelling before-after use case might be helpful for people to better judge.
May 15, 2014 at 9:45 PM
MgSam wrote:
struct MetricKey(String Symbol, String Date);
That would be an enormous win.
Not sure how to resolve questions of whether the created type should have exposed unrestricted fields, exposed read-only fields, exposed read-only properties, or exposed read-write properties. If one is defining a structure type to serve as an aggregation of related-but-independent fields, an exposed-field structure is a perfect fit, and syntax like that quoted would very nicely indicate that the purpose of the struct would be to aggregate related-but-independent fields. Unfortunately, some people think that all types should behave like reference types, and code should never take advantage of situations where the semantics of aggregate types would better fit the task at hand.
May 15, 2014 at 10:44 PM
I think the ability to deconstruct entities will be hard to make in C#. I'll be happy with a more mundane solution that focuses on matching types, with optional conditions and returning values.

Something like:
var area = match shape
           when (Rectangle r) then r.Width * r.Heigth
           when (Circle c) then c.Radius * c.Radius * Math.Pi
           when (Triangle t) and (t.IsRectangle || r.IsEqilateral) then t.Base * r.Height / 2
           default throw;    
Note: Polymorphism only works when you are in control of the class, not for classes on libraries.

Maybe something along the lines can be done with the new conditional expressions:
var area = (var r = shape as Rectangle) != null ? r.Width * r.Heigth : 
           (var c = shape as Circle) != c.Radius * c.Radius * Math.Pi :
           (var t = shape as Triangle) && (t.IsRectangle || r.IsEqilateral) ? t.Base * r.Height / 2
           default throw;    
The problem with this alternative is that the declarations will be available in the following statements, while with the match each variable is only visible in his clause.

Also, in which state will be the variable t when shape is a Rectangle? Uninitialized? Will the declaration expression be so expressive?
May 16, 2014 at 5:32 AM
Edited May 16, 2014 at 5:37 AM
If you are going to support immutable records then you need a sensible way of creating new versions. For example in imagined c# 6.0
record Skill(string Name, int Proficiency);
record Person ( string Name, Skill Skill);

var me = new Person { Name = "Brad", Skill = new Skill { Name = "Programming", Proficiency = 5 };
So how can we update __me.Skill.Proficiency from 5 to 7 , given that me is immutable. Without any language support we would have to do
me = new Person { Name = me.Name, Skill = new Skill { Name = me.Skill.Name, Proficiency = __7__ };
which is ugly and prone to errors. What we need are a concept I understand as lenses. I've implemented this for my own projects as a reflection based solution. I would write the above code something like this.
me = me.Set(p => p.Skill.Proficiency, 7);
Note that this is better than the f# with keyword because I can do the update to any level in the nested structure. I can't quite imagine what notation would make the above make sense. Here is an attempt.

me = me..Skill.Proficiency := 7

We can break it down into two parts. Note the double .. this generates a lens onto a root object. So that

Lens<Person,int> lens = me..Skill.Proficiency;

and

Person meUpated = lens := 7;

So a Lens is just a way to generate new nested records by specifying a sub node of the original nested record to focus on. It makes working with immutable types almost as easy as working with mutable ones.

Anway it's just an idea. I do this via reflection in most of my code and it turns out pretty nice.
May 16, 2014 at 5:39 AM
Although some people dislike them because they don't behave like objects, a nice approach to creating simple immutable objects is to encapsulate data within exposed-field structures. Doing that makes it very easy to build a new structure whose contents are identical to the original except for some specific changes, and then assign the structure to a readonly field, thus making all its fields immutable. Too bad the people behind .NET would rather complain that aggregate types don't behave like objects, than fix some of the limitations in .NET's handling of them.
Developer
May 19, 2014 at 6:13 PM
MgSam wrote:
@C-SharpTeam Has there been any further discussion over any of the features discussed here? The proposed record types and syntax are easily the number 1 most useful thing that has been proposed for C# 6.0, at least in my workflow. More so than even primary constructors.
Records are definitely not going in to the next version of the language. However, I hope that we will be launching an open-source "experimental" fork of Roslyn, perhaps as soon as this summer, in which we start adding these proposed features to C# so we can evaluate them more fully for some future language version.
May 19, 2014 at 6:18 PM
An experimental fork sounds like a great idea.

Is the feature set for C# 6.0 tweak-or-remove-only at this point? I assume the idea is to ship C# 6.0 with VS vNext. I just hope C# 7.0 won't be another subsequent 2, 3 year wait.
May 20, 2014 at 1:36 PM
Edited May 20, 2014 at 1:36 PM
Please reconsider adding the simplest record classes to C# 6. After all, how is class Point(int X, int Y); different from existing anonymous types, except that it's not anonymous? And do allow struct Point(int X, int Y); too, mutatis mutandis.

In fact, being semantically different from anonymous types (except for being named and instantiated with constructor syntax) would be a strong argument against them.

One could even argue that any additional feature you add later, e.g. the pattern matching, could be and maybe should be added to anonymous types as well.
Developer
May 20, 2014 at 8:43 PM
MgSam wrote:
Is the feature set for C# 6.0 tweak-or-remove-only at this point? I assume the idea is to ship C# 6.0 with VS vNext. I just hope C# 7.0 won't be another subsequent 2, 3 year wait.
No, there are a number of language features that we still expect to implement for 6.0.

We are currently having discussions about whether language changes can be dribbled out over minor releases, or whether they need to be bundled into larger sets less frequently.
May 21, 2014 at 12:55 PM
Edited May 21, 2014 at 12:57 PM
@nmgafter This is probably obvious from my previous post, but I vote strongly in favor of small releases that come out as the features are implemented, tested, and baked. Incredibly, CPlusPlus* has largely adopted this model, whereby the compiler vendors are implementing features as fast as the standards committee can ratify them. I think the response in the CPlusPlus community has been very positive to this new pace of innovation, and C#/VB/TypeScript would benefit from similar release cycles. If a standards committee from people across the globe can innovate a language as complex as CPlusPlus and quickly deliver positive results, surely the MS language design teams can as well.

*Codeplex is HTML escaping the plus signs so I wrote out the name without them.
May 22, 2014 at 2:51 PM
MgSam wrote:
@nmgafter This is probably obvious from my previous post, but I vote strongly in favor of small releases that come out as the features are implemented, tested, and baked. Incredibly, CPlusPlus* has largely adopted this model, whereby the compiler vendors are implementing features as fast as the standards committee can ratify them. I think the response in the CPlusPlus community has been very positive to this new pace of innovation, and C#/VB/TypeScript would benefit from similar release cycles. If a standards committee from people across the globe can innovate a language as complex as CPlusPlus and quickly deliver positive results, surely the MS language design teams can as well.

*Codeplex is HTML escaping the plus signs so I wrote out the name without them.
Probably not a good comparison as CPlusPlus technically evolves in much slower but larger chunks and it just takes the compiler vendors that long to dribble out the changes to bring their implementations into compliance. There have only been three standards for CPlusPlus published, initially in 1998 then in 2003 and finally in 2011, and the next isn't expected until 2017 (and will most likely be late).
May 22, 2014 at 2:59 PM
Edited May 22, 2014 at 3:01 PM
@Halo_Four Maybe you should look at this page: https://isocpp.org/std/status. You skipped CPlusPlus 2014. My implication was also that this is a model CPlusPlus has adopted recently, so pointing out the span of time between 98', 03', and 11' is not really relevant.

Not to mention, for some of those CPlusPlus '14 features Microsoft and other compilers have already had them implemented and shipped for one or more years.
May 22, 2014 at 5:30 PM
Edited May 22, 2014 at 5:35 PM
MgSam wrote:
@Halo_Four Maybe you should look at this page: https://isocpp.org/std/status. You skipped CPlusPlus 2014. My implication was also that this is a model CPlusPlus has adopted recently, so pointing out the span of time between 98', 03', and 11' is not really relevant.

Not to mention, for some of those CPlusPlus '14 features Microsoft and other compilers have already had them implemented and shipped for one or more years.
True, but a lot of that work is also library and not language where a concurrent approach is definitely better. But even so is there a single compiler that is actually 100% Cxx11 compliant? Visual Cxx 2013 certainly isn't, and it seems that there are minor things that GCC and clang both also don't support. As for experimental implementation of futures, that scares the crap out of me. Do we really want our programming languages to be as volatile as the web has become? At least with C# there has been pretty much one gatekeeper which has interfaced with the standards body. I'd hate for the C# development process to devolve into whatever the W3C or JCP has become where it seems that they are much more concerned with arguing over esotericism than solving real-world problems.
Jun 3, 2014 at 2:21 PM
As some food for thought for a future pattern matching feature, I thought it was interesting what Apple is doing with Swift and it's switch statement.
Jun 3, 2014 at 4:50 PM
VB.net can do all that already especially with the preview.
Jun 3, 2014 at 5:20 PM
AdamSpeight2008 wrote:
VB.net can do all that already especially with the preview.
The syntax is more similar to C# though (being of C lineage). Thus, I thought it interesting to take a look at.
Jun 10, 2014 at 5:36 PM
Will the pattern-matching be able to do the following?
Select Case ( node.Left , node.Right )
  Case ( Integer, Integer )
  Case ( Double, Double )
  Case Else
End Select
Long-hand version
If (TypeOf node.Left Is Integer) AndAlso (TypeOf node.Right Is Integer) Then

ElseIf (TypeOf node.Left Is Double) AndAlso (TypeOf node.Right Is Double) Then

Else

End If
Developer
Jun 11, 2014 at 1:14 AM
AdamSpeight2008 wrote:
Will the pattern-matching be able to do the following?
Select Case ( node.Left , node.Right )
  Case ( Integer, Integer )
  Case ( Double, Double )
  Case Else
End Select
Long-hand version
If (TypeOf node.Left Is Integer) AndAlso (TypeOf node.Right Is Integer) Then

ElseIf (TypeOf node.Left Is Double) AndAlso (TypeOf node.Right Is Double) Then

Else

End If
We are working on matching with tuples as part of this, but it is unlikely we'd end up with precisely the syntax you proposed. In part because your cases did not introduce any variables.
Jun 11, 2014 at 2:46 AM
Edited Jun 11, 2014 at 3:59 AM
@nmgafter

Introducing a new variable should be optional in my opinion, otherwise it's going start getting messy.
In the following example the variables are introduced via With { } parameter.
Case Select (node.Left, node.Right )
  Case (Integer,Integer ) When ( some_x = 1000) With {a,b} :   ' /* a::= node.Left   b::= node.Right */
  Case (Integer,Integer ) When ( some_x > 1000) With { ,b} :   ' /* b::= node.Right */
Make is easier to both express (don't care) and introduce variables.

In long hand.
If( (TypeOf node.Left  Is Integer ) AndAlso (TypeOf node.Right Is Integer) ) AndAlso
    ( some_X = 1000) ) Then
  ' Create variables for contents 
  Dim a = node.Left
  Dim b = node.Right
  ' a and b are now lexically scoped to be here
  ...
Else If( (TypeOf node.Left  Is Integer ) AndAlso (TypeOf node.Right Is Integer) ) AndAlso
    ( some_X > 1000) ) Then
  ' Create variables for contents 
  Dim b = node.Right
  ' b is now lexically scoped to be here, and also is a different b to the one in previous [if clause]
  ...
...

I think it is useful to consider what would the equivalent If ... Then ... Else If ... Else ... End If would be,

Also only allow one of these types of case select clause per a clause and on its own.

Otherwise you could have the situation where different variables are introduce depending on the clause.
Case (Integer,Integer) With New {a,b} , (Double, Double) With New {x,y} :
  ' Error 
The you would then have the issue of how to express fall-through the following clause?
Select Case Obj
Case (Integer) : GoTo Case caseA ' The GoTo Case scopes the GoTo be only in a following case clause.
Case (Double)  : GoTo Case caseA ' Remove the Case and it becomes an ordinary GoTo
Case (Single)  : GoTo Case caseA ' Trying to GoTo a clause beyond the next labelled case clause is an error,
Case (String)
caseA:
  doStuff
Some would see that as "GoTo WTF!" , but I'd think of it a straight-jacketed insanity.
Sep 4, 2014 at 11:01 AM
Edited Sep 4, 2014 at 1:04 PM
I've been think that the new constructs of As and When should be restricted to only one per a case-statement.

Basic BNF
constant         ::=
identifier       ::=
type_name        ::=
const_or_i       ::= constant | identifier
case_simple      ::= constant
is_Expr          ::=

dont_care        ::= "__"
keyword_IS       ::= "Is"
keyword_TO       ::= "To"
keyword_WITH     ::= "With"
keyword_As       ::= "As"

type_id          ::= identifier | dont_care
type_ids         ::= type_id | ( "{" type_id ("," type_id )* "}" )
type_names       ::= type_name | ( "(" type_name ( "," type_name )* ")" )

case_is          ::= keyword_IS ( const_or_id | is_Expr )
case_range       ::= const_|_id keyword_To const_|_id

case_type1       ::= type_names keyword_WITH "{" type_id? ("," type_id? ) * "}"
case_type0       ::= type_names keyword_As type_ids 

case_simpleExpr  ::= case_simple | case_is | case_range
case_simpleExprs ::= case_simpleExpr ("," case_simpleExpr)*
case_complexExpr ::= case_simple | case_type0
case_guard       ::= case_complexExpr "When" expr<bool>

case_statement   ::= "Case" case_complex | case_guard
Allow the current set ( < Roslyn Preview ) of case statements which uses As as the mechanism of introducing variables into scope.
Select Case X
  Case 1
  Case 2
  Case 3,4
  Case Is < 1
  Case Is > 100
  Case 5 To 10
  Case 11 To 20, 31 To 40
End Select
The following are restricted to only one per a case statement.

Type Matching
The capability of matching against a type or set of types
Select Case TypeOf ( objT  ) ' <- Special form of Select Case
  Case Integer As my_integer 
  Case Double  As my_double
  Case (Double,Integer)  As ( my_double, my_integer )
    ' NOTE: This would be a an error as only one parameter was provided.
    '     : by the Case TypeOf(  ) statement
End Select

Select Case TypeOf ( node.Left , node.Right  ) ' <- Special form of Select Case
  Case Integer As my_integer
  ' NOTE: Invalid as more than one type argument was supplied
  Case Double  As my_double
  ' NOTE: Invalis as more that one type argument was supplied
  Case (Double,Integer)  As ( my_double, my_integer )
  ' NOTE: my_double my_integer are in scope.
End Select
The scoping of the introduced variable is restricted to the code statement itself and it's body.

Guarded Cases

The body of the case statement is only select if the predicate is true.
Select Case  x
  Case 0 When y > 0
  Case 0 When y <= 0
  Case 1, 2,  3 When y > 0
  Case 1 When foo = true, 2 When foo = false
  ' NOTE: Invalid only one guard per a case statement
End Select
The invalid case-statement could always be expanded into to separate case -statements
Case 1 When Foo = True
Case 2 When foo = false
If we discard the invalid case-statement what would the equivalent IF-based construct would be
If ( x = 0 ) Then
   If ( y > 0 ) Then
      ' Body of first case statement
   End If
Else If ( x = 0 ) Then
   IF ( y <= 0 ) Then
      ' Body of second case statement
   End If
Else IF  (( x = 1) OrElse ( x =2 ) OrElse ( x = 3 ) ) Then
   If  ( y > 0 ) Then
      ' Body of third case statement
   End If
End If   
Select Case TypeOf( node,Left , node.Right )
  Case (Integer, Integer ) As ( L, R ) When  (L >= 0) AndAlso (R >= 0)
    ' do this
End Select 
Let's looks at what the equivalent If-based construct would be
If ( TypeOf( node.Left ) Is Integer ) AndAlso
   ( TypeOf( node.Right ) Is Integer ) Then
     ' Introduce the variables
     Dim L = DirectCast( node.Left  , Integer )
     Dim R = DirectCast( node.Right , Integer )
     ' Since the introduced are in scope we can use then in the guard predicate.
     If ( ( L >= 0 ) AndAlso ( R >= 0 ) Then
       ' Body of case statement
     End If 
End If 
The predicate of the guard can use any variable that is available and in scope at the particular section of code.
Hence the previous restriction on matching against type(s), which could (if it was allowed) is introduce variable of the same name but different types).

With instead of As

Same BNF as before expect that
case_complexExpr ::= case_simple | case_type1
Instead of using As lets use and existing construct With_ from object Initializer syntax with a tweak.
Select Case TypeOf( node.Left, node.Right )
  Case ( Integer , Integer ) With {L, R} When ( (L>=0) AndAlso (R >= 0) )
End Select
I think this form of the syntax is more VB-like .
Sep 15, 2014 at 5:08 PM
What is the current status of the Pattern Matching language feature? As the is seems to be a little quiet on the vb.net front.
Developer
Sep 17, 2014 at 3:35 AM
We have a prototype of pattern matching (and records) for C# which has been described elsewhere. The design transcribes to VB very nicely but no work has started on a VB prototype. The proposal has not yet been considered by the language design committees for inclusion in any particular release of the languages. That discussion will begin after the current set of language changes have been shipped in a product.
Coordinator
Sep 17, 2014 at 5:13 AM
AdamSpeight2008 wrote:
What is the current status of the Pattern Matching language feature? As the is seems to be a little quiet on the vb.net front.
Hey AdamSpeight2008,

As nmgafter said, the pattern matching discussion is still early in the design life cycle, though we've actually been having conversations about pattern matching for both VB and C# back and forth for the last year or so and will continue to think about both as the discussion evolves.

We think of pattern matching, records/tuples, and destructuring/match operators as part of a cohesive story that could advance the state of the .NET platform in the way that features like Generics, LINQ/lambdas, and Async have. Once you have a syntax for declaring libraries that expose these kinds of data structures and syntax in a language for consuming them, the way .NET developers design and interact with the ecosystem fundamentally leaps forward and platforms and libraries are rarely written in the same way again.

As such, it would be critical to both our VB and C# developer communities to ensure that we deliver those features simultaneously to ensure both communities continue to have full access to the richness of the .NET ecosystem (as we have with other paradigm-shifting language features in prior releases). It is also why it's important to consider such features in coordination with our 1st-party platforms such as ASP.NET and the .NET Base Class Library so that the languages and .NET Framework continue to co-evolve together.

On the topic of pattern matching specifically we've been most interested in how pattern matching could integrate with the existing VB Select Case construct. The prototype Select Case improvements we shipped as part of the "Roslyn" End User Preview for Visual Studio 2013 are actually an artifact of this very discussion. For both languages it's very important to us while designing features for Visual Studio "14" that we consider how they would work together with features we know we're strongly consider beyond VS "14" so that the languages continue to have cohesive feature sets and self-consistent styles.

Regards,

Anthony D. Green, Program Manager, Visual Basic & C# Languages Team
Sep 18, 2014 at 1:21 PM
In my opinion this only adds some syntactic sugar and in some very nitche cases and you shouldn't change old code you should adopt it if the scenario arrives and helps your paradigm, otherwise its just there.

This is similar to exception filters and comes from the need to be able to have F# patterns implemented in C#.

Lets consider the 'is' overload, this construct allows for special filtering by the compiler but is in no way more performant then simply filtering the clause yourself.

E.g

'if (x is Something) return (x as Something).Value > 0'

Becomes something like

'x is (Something, 1)'

The problem anyone failed to realise is that when x is Something via the virtues of something else either via inheritance or interface and those properties are virtual this could be tricky to debug especially when each type may have a im implicit or explicit cast overload for Something.

Additionally the 'Record' concept is nothing different than an anonymous type which has a type with the underlying.

E.g. a Record of Something simply provides a wrapper around Record with your properties as immutable and a hashcode. (only again as syntactic sugar this time to support Primary constructors which have another set of gotchas, e.g. when the inheriting type defines a property which is generated by the derivative type, hiding occurs and you end up having to type everything out anyway)

I wish that things which actually need attention were revisited e.g. events (maybe events which can be turned on and off via a property 'Enabled' and the ability to to stop propagation after a certain handler)

Recursive function optimization or a tail keyword to force it to be applied.


Nonetheless interested to see where and how this gets used in reality.
Sep 19, 2014 at 2:49 AM
juliusfriedman wrote:
Recursive function optimization or a tail keyword to force it to be applied.
Tail recursion should IMHO be applied only when a tail return statement is used. There are a variety of cases which may affect a compiler's ability to use tail recursion; program which requires tail recursion for correctness (i.e. because it would otherwise blow the stack) should explicitly say that, so that anything which would in future prevent tail recursion would cause a compile time error, rather than generating code that will bomb at run-time.
Sep 19, 2014 at 3:21 AM
Edited Sep 19, 2014 at 3:21 AM
I proposed a tail return as well as a recursive keyword well before Roslyn was released, thanks for the comment!