Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Outline of C# 7 demo at MVP Summit 2015-11-02 #6505

Closed
gafter opened this issue Nov 1, 2015 · 56 comments
Closed

Outline of C# 7 demo at MVP Summit 2015-11-02 #6505

gafter opened this issue Nov 1, 2015 · 56 comments

Comments

@gafter
Copy link
Member

gafter commented Nov 1, 2015

Here’s an outline of a demo at the MVP summit on 2015-11-02

Let’s talk about local functions.

A method often has other private “helper” methods that are used in its implementation. Those methods are in the scope of the enclosing type, even though they are only intended to be used in a single place. Local functions allow you to define a function where it is used. For example, given a helper method

static int Fib(int n)
{
    return (n < 2) ? 1 : Fib(n - 1) + Fib(n - 2);
}

Or, using the new syntax added in C# 6:

static int Fib(int n) => (n < 2) ? 1 : Fib(n - 1) + Fib(n - 2);

And the method that it is used in

static void Main(string[] args)
{
    Console.WriteLine(Fib(7));
    Console.ReadKey();
}

In C# 7 you’ll be able to define the helper function in the scope where it is used:

static void Main(string[] args)
{
    int Fib(int n) => (n < 2) ? 1 : Fib(n - 1) + Fib(n - 2); //!

    Console.WriteLine(Fib(7));
    Console.ReadKey();
}

Local functions can use variables from the enclosing scope:

static void Main(string[] args)
{
    int fib0 = 1; //!
    int Fib(int n) => (n < 2) ? fib0 : Fib(n - 1) + Fib(n - 2);

    Console.WriteLine(Fib(7));
    Console.ReadKey();
}

You can imagine having to pass such state as additional parameters to a helper method if it were declared in the enclosing type, but local function can use local variables directly.

Capturing state like this does not require allocating frame objects on the heap as it would for delegates, or allocating a delegate object either, so this is much more efficient than what you would have to do to simulate this feature by hand.

Let’s talk about pattern matching.

With object-oriented programming, you define a virtual method when you have to dispatch an operation on the particular kind of object. That works best when the author of the types can identify ahead of time all of the operations (virtual methods) on the types, but it enables you to have an open-ended set of types.

In the functional style, on the other hand, you define your data as a set of types without virtual functions, and define the functions separately from the data. Each operation provides an implementation for each type in the type hierarchy. That works best when the author of the types can identify ahead of time all of the shapes of the data, but it enables you to have an open-ended set of operations.

C# does a great job for the object-oriented style, but the functional style (where you cannot identify all the operations ahead of time) shows up as a frequent source of awkwardness in C# programs.

Let’s get really concrete. Suppose I have a small hierarchy of types

// class Person(string Name);
class Person
{
    public Person(string name) { this.Name = name; }
    public string Name { get; }
}

// class Student(string Name, double Gpa) : Person(Name);
class Student : Person
{
    public Student(string name, double gpa) : base(name)
        { this.Gpa = gpa; }
    public double Gpa { get; }
}

// class Teacher(string Name, string Subject) : Person(Name);
class Teacher : Person
{
    public Teacher(string name, string subject) : base(name)
        { this.Subject = subject; }
    public string Subject { get; }
}

The comments, by the way, shows a possible future syntax we are considering for C# 7 that we call records. We’re still working on records, so I won’t say more about that today. Here is an operation that uses these types

static string PrintedForm(Person p)
{
    Student s;
    Teacher t;
    if ((s = p as Student) != null && s.Gpa > 3.5)
    {
        return $"Honor Student {s.Name} ({s.Gpa})";
    }
    else if (s != null)
    {
        return $"Student {s.Name} ({s.Gpa})";
    }
    else if ((t = p as Teacher) != null)
    {
        return $"Teacher {t.Name} of {t.Subject}";
    }
    else
    {
        return $"Person {p.Name}";
    }
}

And for the purposes of the demo, a client of that operation

static void Main(string[] args)
{
    Person[] oa = {
        new Student("Einstein", 4.0),
        new Student("Elvis", 3.0),
        new Student("Poindexter", 3.2),
        new Teacher("Feynmann", "Physics"),
        new Person("Anders"),
    };
    foreach (var o in oa)
    {
        Console.WriteLine(PrintedForm(o));
    }
    Console.ReadKey();
}

Note the need to declare the variables s and t ahead of time in PrintedForm. Even though they are only used in one branch of the series of if-then-else statements, they are in scope throughout. That means that you have to think up distinct names for all of these temporary variables. As part of the pattern-matching feature we are repurposing the “is” operator to take a pattern on the right-hand-side. And one kind of pattern is a variable declaration. That allows us to simplify the code like this

static string PrintedForm(Person p)
{
    if (p is Student s && s.Gpa > 3.5) //!
    {
        return $"Honor Student {s.Name} ({s.Gpa})";
    }
    else if (p is Student s)
    {
        return $"Student {s.Name} ({s.Gpa})";
    }
    else if (p is Teacher t)
    {
        return $"Teacher {t.Name} of {t.Subject}";
    }
    else
    {
        return $"Person {p.Name}";
    }
}

Now the temporary variables s and t are declared and scoped to just the place they need to be. Unfortunately we’re testing against the type Student more than once. Back to that in a moment.

We’ve also repurposed the switch statement so that the case branches are patterns instead of just constants (though constants are one kind of pattern). That enables you to use switch as a "type switch":

static string PrintedForm(Person p)
{
    switch (p) //!
    {
        case Student s when s.Gpa > 3.5 :
            return $"Honor Student {s.Name} ({s.Gpa})";
        case Student s :
            return $"Student {s.Name} ({s.Gpa})";
        case Teacher t :
            return $"Teacher {t.Name} of {t.Subject}";
        default :
            return $"Person {p.Name}";
    }
}

The compiler is careful so that we don’t type-test against Student more than once in the generated code for switch.

Note the new when clause in the switch statement.

We’re also working on an expression equivalent to the switch statement, which is like a multi-branch ?: operator for pattern matching:

static string PrintedForm(Person p)
{
    return p match ( //!
        case Student s when s.Gpa > 3.5 :
            $"Honor Student {s.Name} ({s.Gpa})"
        case Student s :
            $"Student {s.Name} ({s.Gpa})"
        case Teacher t :
            $"Teacher {t.Name} of {t.Subject}"
        case * :
            $"Person {p.Name}"
    );
}

Because you sometimes need to throw an exception when some condition is unexpected, we’re adding a throw expression that you can use in a match expression:

    return p match (
        case Student s when s.Gpa > 3.5 :
            $"Honor Student {s.Name} ({s.Gpa})"
        case Student s :
            $"Student {s.Name} ({s.Gpa})"
        case Teacher t :
            $"Teacher {t.Name} of {t.Subject}"
        case null :
            throw new ArgumentNullException(nameof(p)) //!
        case * :
            $"Person {p.Name}"
    );

Another useful kind of pattern allows you to match on members of a type:

    return p match (
        case Student s when s.Gpa > 3.5 :
            $"Honor Student {s.Name} ({s.Gpa})"
        case Student { Name is "Poindexter" } : //!
            "A Nerd"
        case Student s :
            $"Student {s.Name} ({s.Gpa})"
        case Teacher t :
            $"Teacher {t.Name} of {t.Subject}"
        case null :
            throw new ArgumentNullException(nameof(p))
        case * :
            $"Person {p.Name}"
    );

Since this is an expression, we can use the new “=>” form of a method. Our final method is

static string PrintedForm(Person p) => p match (
    case Student s when s.Gpa > 3.5 :
        $"Honor Student {s.Name} ({s.Gpa})"
    case Student { Name is "Poindexter" } :
        "A Nerd"
    case Student s :
        $"Student {s.Name} ({s.Gpa})"
    case Teacher t :
        $"Teacher {t.Name} of {t.Subject}"
    case null :
        throw new ArgumentNullException(nameof(p))
    case * :
        $"Person {p.Name}"
    );

In summary:

  • Local functions (capturing state is cheap)
  • Pattern-matching
    • Operator is
    • Switch, “when” clauses
    • match expression
    • Patterns
      • Constant patterns e.g. 1 in an ordinary switch
      • type-match patterns e.g. Student s
      • property patterns e.g. Student { Name is "Poindexter" }
      • wildcard e.g. *
  • Still working on
    • Records, algebraic data types
    • Tuples
@gafter
Copy link
Member Author

gafter commented Nov 2, 2015

@AnthonyDGreen FYI the features discussed here are working in https://github.com/gafter/roslyn/tree/features/patterns and will be demonstrated during the keynote tomorrow morning.

@Quantumplation
Copy link

Will there be a video later, for those of us not fortunate enough to attend the summit?

@stepanbenes
Copy link

Good summarization of planned features. I'd like to see the video from the keynote too. Examples are well chosen and understandable. However, I don't see any mention of user-defined operator is and possibility to override the is operator. I think it is quite important part of pattern matching proposal...

@dsaf
Copy link

dsaf commented Nov 2, 2015

Hopefully summit will be used as an occasion to release Visual Studio 2016 CTP1 😃. Unless C# is going to be updated independently via NuGet in future!

@khalidabuhakmeh
Copy link

I really like the pattern matching syntax

@gafter
Copy link
Member Author

gafter commented Nov 3, 2015

Sorry, the keynote has NDA bits so no video can be published.

@leppie
Copy link
Contributor

leppie commented Nov 3, 2015

@gafter Does the features/patterns branch also contain the implementation for local functions?

@whoisj
Copy link

whoisj commented Nov 3, 2015

@gafter exciting :-)

oh why did I leave Redmond right before this?

@gafter
Copy link
Member Author

gafter commented Nov 3, 2015

I did the demo from the branch https://github.com/gafter/roslyn/tree/features/patterns . That includes all the features currently in https://github.com/dotnet/roslyn/tree/future (binary literals, digit separators in literals, local functions) and a handful of features related to pattern matching (patterns, extended operator is, extended switch with when clauses, expression-based match, and throw expression). It does not include support for user-defined operator is, as we are considering different ways that might be expressed, for example possibly using a tuple return value instead of a binch of out parameters.

Right now there is no easy way for you to try out features from a branch inside Visual Studio, but we are working on making that possible. We expect it will be working in VS2015 RTM.

@tpetrina
Copy link

tpetrina commented Nov 3, 2015

Might be a stupid question, but how can you enable experimental features when compiling with csc.exe? Or how to enable it in general? When building from sources.

@gafter
Copy link
Member Author

gafter commented Nov 3, 2015

If you try to use a feature that is not officially supported the error message will tell you what to do. For example, to enable pattern matching, you need to use the compiler flag /features:patterns.

I've also hacked in a feature on my branch (only) so that if you add __DEMO__ to the set of defined preprocessor symbols in the build (not just in a source file, it has to be on the command line or in the build configuration) then all of the implemented features are enabled. I'll ask around here about how people feel about adding that to the future branch (and therefore indirectly to all the language prototypes).

@xperiandri
Copy link

Everything except case Student { Name is "Poindexter" } : //! looks perfect
This looks very confusing

@jods4
Copy link

jods4 commented Nov 18, 2015

Just saw that in the keynote today.

First reaction when seeing the first PrintedForm: wow what a bad example. A succession of if (x is A) else if (x is B) else when they all inherit from the same base class is screaming "learn OO and use polymorphism". Make PrintedForm a virtual method on Person and you don't really have a "bad" example code to improve.

Mind you, I think pattern matching is a nice addition to C#. It will be useful with record types and other things that don't inherit from the same class, when using complex conditions and deep structural matching.

I just thought that the example presented was not great and started with code that I wouldn't want to see in our codebase in the first place.

@gafter
Copy link
Member Author

gafter commented Nov 18, 2015

@jods4 You cannot add a virtual method for every piece of code in the world that wants their own specialized printed form. There is no way to "monkey-patch" C#.

@jods4
Copy link

jods4 commented Nov 18, 2015

@gafter I agree with you.
There are always situations or constraints where you have to do things in a less "nice" way.

That said, for a general introduction of a new feature to a broad audience, I felt like the example used to demonstrate the feature was maybe not the best choice.

There was no "constrained" context given (and there shouldn't) and the demo felt very much like your standard in-house LOB app, with the source code of three classes Person etc. shown first, then the PrintedForm method. At this point, as much as I like the introduced feature, I cringed.
I did not like the code shown, I knew exactly where she was headed for and I thought "I wish they used a better example".

@gafter
Copy link
Member Author

gafter commented Nov 19, 2015

@jods4 Yes, it is typical in LDB applications to overuse object-oriented features, and take what should logically be code needed in one place locally and instead smear it across the type hierarchy in the form of virtual methods. There are many people so wedded to the object-oriented religion that they do not see the problem with this from a software engineering perspective. We're trying to make the C# language more usable for developers who want to keep local concerns local.

@jods4
Copy link

jods4 commented Nov 20, 2015

@gafter I'm not willing to get into an argument... Here's just my point of view, from someone who has been creating small to large LOB apps in enterprises for the last 10 years.

I actually do see the kind of code you used relatively often in LOBs (big if (x is A) else if (x is B) else).

  1. This kind of code just looks bad. It undeniably looks better with pattern matching, but still.
  2. Short, small methods with low cyclomatic complexity are better in several regards.
  3. But to me the most important problem: people forget about all the scattered logic and there is no documented contract for what a Person should do in a large system.

Say you need to add a new entity for teaching assistants (TA). Will you remember to go in PrintedForm add the relevant case? In a large, complex system there might be tens of such "local concerns". In a team environment how can you be aware of all? If you declared virtual methods in Person (better: abstract ones when you can), then you cannot miss it when creating the TA class.

I know object-oriented design is not a perfect fit for all problems. Even more so without advanced features such as double-dispatch.
Something as simple as the visitor pattern is not great. Wire transfer objects (e.g. from JSON) often don't have behavior -> functional paradigm / pattern matching will work great there. What if someone is a Student and a TA? And so on...

But OO is a successful paradigm that works well in many cases, inluding IMHO the one that was presented as a motivational example for the pattern matching feature.

Out of curiosity, in few words, can you give example for problems cited in

There are many people so wedded to the object-oriented religion that they do not see the problem with this from a software engineering perspective.

BTW I think that the "multi-paradigms" approach of modern language design is great. I really enjoy the bits of functional programing that found their way into C# (OO at its core), they make C# awesome. Maybe a touch of AOP support would be cool.

@gafter
Copy link
Member Author

gafter commented Nov 20, 2015

@jods4

Out of curiosity, in few words, can you give example for problems cited in

The Java compiler in version 2 was a quintessentially object-oriented program. There were class types for each syntactic construct in the language, and there were virtual methods for every phase of the compiler. As a result the code for each node type was huge and mixed concerns from across the whole compiler (because it contained virtual method implementations for every phase). In order to understand any particular phase of the compiler you'd have to read every source file (because the phase's virtual methods were spread across all the node types). It became a nightmare to maintain because you could hardly do anything without understanding everything.

When we replaced it with a brand new compiler in version 3, it followed a more functional style. The node types contained their necessary data plus a couple of virtual methods to support the visitor pattern. Each phase of the compiler was completely contained in its own class and source file. The compiler was much, much easier to understand and maintain as a consequence. The total sources were between one third and one half the size of the previous compiler, and the compiler also implemented generics (which we didn't expose until version 5).

Roslyn follows a design much more similar to the Java 3 compiler. Roslyn's data structures (syntax trees, semantic nodes, symbol tables, etc) do not expose virtual methods for all of the phases of the compiler. They are (mostly) fairly simple data holder classes.

I do understand your concern about ensuring that changes to the type hierarchy are reflected in every switch statement that intends to handle all cases. When attempting to program in the functional style in a language such as Java or C# that does not support pattern-matching, you achieve that using the visitor pattern. For languages that do support the functional style, you achieve that using algebraic data types (ADTs). You can think of that as a way of declaring a set of types together, and then saying "that's all of them". Then you need some way to write a switch statement that intends to handle all the cases, so that the compiler can complain if you miss some cases.

We are working on ADTs and completeness checking (See #188 and #6739) as part of this feature set. Due to time limitations it just wasn't shown as part of this demo.

@gafter
Copy link
Member Author

gafter commented Nov 20, 2015

As for AOP, we're working on #5561 and #5292. Once those are available I expect you'll see AOP add-ons become available in very short order.

@sharwell
Copy link
Member

Having been in the demo, I can confirm that the description above is at least as comprehensive as what we saw. The only thing I can think of which was mentioned and related to the ongoing work on C# 7 was a note that it should be possible to declare local functions at the end of the method which uses them, such as the following.

static void Main(string[] args)
{
    Console.WriteLine(Fib(7));
    Console.ReadKey();

    int Fib(int n) => (n < 2) ? 1 : Fib(n - 1) + Fib(n - 2); //!
}

This feature is not yet implemented in the compiler so it wasn't actually shown in the demo.

On a side note, I was watching throughout the Summit for cases where a presentation contained information about work in the roslyn/corefx/coreclr repositories which was planned but never put in an issue for open discussion. I'm happy to say that I didn't see any such cases. For anyone actually following these repositories, everything we saw as "new" on these subjects at the Summit you were pretty well aware of months ago.

@jods4
Copy link

jods4 commented Nov 20, 2015

@gafter I see where you're coming from.

The right tool for the right job... Compilers have lots of similar "types" (in the form of AST nodes, etc.) and relatively fewer operations that they want to perform (type propagation and checking, optimizations passes, etc.). Moreover each operation really wants to be contained in a single unit (e.g. coding all the constant folding rules in one place). You can say the code is "operation" centric.

LOB tend to have far fewer similar business "types" but relatively more "operations". They can easily be seen as more "data" centric (usually).

What compilers do is a kind of Visitor pattern and I think it is telling that in 10 years I used the Visitor pattern several times, but never on business entities.

And I think this sums it up: what really nagged me in the presentation, as a LOB developer, was that the example seemed like a LOB domain and the (short) code example did not seem appropriate if we had to code it in C# 6 today.

I love that ADT are coming to C# with completeness. That makes them a compelling new option in our arsenal. Completeness is key, for reasons I gave in a previous comment above. With complex domains that you often don't master completely in large projects spanning many years, static guarantees of correctness is very much wanted!

@dsaf
Copy link

dsaf commented Nov 23, 2015

VS Connect 2015 - Short C# Pattern Matching demo

https://channel9.msdn.com/Events/Visual-Studio/Connect-event-2015/010

Good stuff starts at 0:28:42 and ends at 0:33:06.

@SamirHafez
Copy link

Regarding pattern matching:

static string PrintedForm(Person p)
{
    switch (p) //!
    {
        case Student s when s.Gpa > 3.5 :
            return $"Honor Student {s.Name} ({s.Gpa})";
        case Student s :
            return $"Student {s.Name} ({s.Gpa})";
        case Teacher t :
            return $"Teacher {t.Name} of {t.Subject}";
        default :
            return $"Person {p.Name}";
    }
}

is there a need for:

return p match ( //!
        case Student s when s.Gpa > 3.5 :
            $"Honor Student {s.Name} ({s.Gpa})"
        case Student s :
            $"Student {s.Name} ({s.Gpa})"
        case Teacher t :
            $"Teacher {t.Name} of {t.Subject}"
        case * :
            $"Person {p.Name}"
    );

Instead of something like return switch (p)... (in the first case). The compiler can check that each switch case adheres to the return type of the method. Something like:

    return switch (p) //!
    {
        case Student s when s.Gpa > 3.5 :
            $"Honor Student {s.Name} ({s.Gpa})";
        case Student s :
            $"Student {s.Name} ({s.Gpa})";
        case Teacher t :
            10; //Compile error
        default :
            $"Person {p.Name}";
    }

@gafter
Copy link
Member Author

gafter commented Dec 9, 2015

@SamirHafez You're asking if there should be an expression form of switch instead of a new keyword match? Yes, I agree. However, the proposed syntax (#5154) is

expression switch( case_pattern_:_expression_ ...)`

curly braces and semicolons don't make much sense for an expression context. Those make more sense for statements; for the expression form of switch, we use parens and commas.

@SamirHafez
Copy link

I see. You're right, it doesn't really make much sense as a statement.

However, is there not a chance match would just canibalize switch useful cases? Like F# does fine without a switch, so should C# in the long run?

@iam3yal
Copy link

iam3yal commented Jan 13, 2016

Great stuff, thank you!

@DavidArno
Copy link

@SamirHafez,

It's interesting that you ask a very similar question to what I was about to post, save yours is the complete reverse to mine.

I'd argue that (a) given C# 7 will support:

return p match ( //!
    case Student s when s.Gpa > 3.5 :
        $"Honor Student {s.Name} ({s.Gpa})"
    case Student s :
        $"Student {s.Name} ({s.Gpa})"
    case Teacher t :
        $"Teacher {t.Name} of {t.Subject}"
    case * :
        $"Person {p.Name}"
);

and (b) pattern matching is a functional language feature intended to supply polymorphic expression handling, why on earth are the C# 7 designers shoe-horning this feature into the imperative switch statement and almost treating the "proper" expression-based use of pattern matching as an after thought?

@DavidArno
Copy link

In most of the descriptions of how one might use pattern matching in C# 7, I come across statements like "Suppose I have a small hierarchy of types" and the example then uses a set of types that utilise that much hated by many feature, inheritance.

In none of the examples I've seen so far has there been any mention of discriminated unions. Does anyone know whether C# 7 will only implement a poor-man's version of pattern matching that is limited to inheritance models, or will discriminated unions be implemented as part of the work being done on records?

@JoshVarty
Copy link
Contributor

I'm new to pattern matching so I don't understand the idea behind:

 return p match (
        ...
        case Student { Name is "Poindexter" } : 
            "A Nerd"
        ...
    );

What's the advantage over:

 return p match (
        ...
        case Student s when s.Name == "Poindexter"  :
            "A Nerd"
        ...
    );

@bbarry
Copy link

bbarry commented Feb 11, 2016

@JoshVarty

s is an identifier. It has scoping rules that need to be met and could potentially conflict with some other identifier in a scope somewhere. It could be captured into a closure and even live on in memory due to being captured by some long lived object. Since the name really doesn't matter, why should it be specified and have to deal with these issues?

@JoshVarty
Copy link
Contributor

I'd argue that if there are two ways of doing the same thing and we have the option of introducing just one way, we should only introduce one way. (I don't really buy the naming issue or the accidental closure issue).

If the first approach (matching on members) allows us to gain some much-needed functionality that the second doesn't, then that makes sense. It looks like it's probably required for user-defined is-operators

@HaloFour
Copy link

@JoshVarty

In the property pattern all of the properties can be matched to any patterns, not just constant patterns, which can support much more complicated logic than could be expressed easily in a single guard expression:

return p switch (
    case Student { Course is OnlineCourse { Professor is TenuredProfessor { Name is var name } } } : name,
    case * : "N/A"
);

@bbarry
Copy link

bbarry commented Feb 12, 2016

But they aren't really doing the same thing. The former matches a pattern and the latter matches a pattern and introduces a variable of a particular type (I'm not really convinced either), It's the kind of subtlety between the difference of linq statements and extension methods and I suspect will be regarded by future coders in similar ways.

@DavidArno
Copy link

@bbarry,

That's a really good analogy. I'd been wondering why have two ways of pattern matching the same thing. However, having both the query syntax and method-chain syntax for linq is really useful: sometimes one is the better solution to a task; other times the other way is the better. So maybe this'll prove the case with pattern matching too.

@alrz
Copy link
Member

alrz commented Feb 12, 2016

There are unlimited ways of doing the same thing in a language. Obviously it doesn't solve a problem that you don't have.

@DavidArno
Copy link

@alrz,

Some though (goto, switch, threads, inheritance, singletons...) take the problem you had and give you another to deal with... ;)

@robinsedlaczek
Copy link
Contributor

Is there an answer to the question when the next design meeting will be and when we can read next meeting notes? Would be cool to see where you are currently and what's going on. Many thanks in advance.

@DavidArno
Copy link

@robinsedlaczek,

This is a really good question that I too would like to see the answer to. We really need more visibility of the design meetings and more regular design notes. Plus the documentation around the most likely new features in C# 7 could do with an overhaul and update to better reflect the current plans of the design team.

Fingers crossed it all appears at some point this month.

@iam3yal
Copy link

iam3yal commented Feb 15, 2016

@robinsedlaczek, @DavidArno don't know if it's much and whether you seen this video but Mads Torgersen spoke about some of what's planned here https://blogs.msdn.microsoft.com/dotnet/2016/01/19/on-net-172016-mads-torgersen/

@HaloFour
Copy link

@gafter

re: #6505 (comment)

This response is a bit late but I wanted to comment that the one thing that I don't like about considering tuples in place of out parameters for custom is operators is that will seemingly tie the feature to a frameworks that support System.ValueTuple<...>. I don't believe that I've seen any other aspect of pattern matching that force any specific framework so it would be kind of a shame to require it here.

@Thaina
Copy link

Thaina commented Mar 9, 2016

As for this

static string PrintedForm(Person p)
{
    if (p is Student s && s.Gpa > 3.5) //!
    {
        return $"Honor Student {s.Name} ({s.Gpa})";
    }
    else if (p is Student s)
    {
        return $"Student {s.Name} ({s.Gpa})";
    }
    else if (p is Teacher t)
    {
        return $"Teacher {t.Name} of {t.Subject}";
    }
    else
    {
        return $"Person {p.Name}";
    }
}

Can we just make it like Kotlin language that, in any block which contain is type checking, that variable will transform to that type in and only in that block

So all we need is just

static string PrintedForm(Person p)
{
    if (p is Student && p.Gpa > 3.5) //p already become Student just after &&
        return "Honor Student " + p.Name + " (" + p.Gpa + ")";
    else if (p is Student) //p become Student in this block
        return "Student " + p.Name + " (" + p.Gpa + ")";
    else if (p is Teacher) //p become Teacher in this block
        return "Teacher " + p.Name + " of " + p.Subject;
    else return "Person " + p.Name;
}

@HaloFour
Copy link

HaloFour commented Mar 9, 2016

@Thaina

That would be a breaking change since existing code which performs comparisons like this might find the compiler resolving different members or overloads using the variable.

@bbarry
Copy link

bbarry commented Mar 9, 2016

Here is a pathological case that would work differently in existing code vs if that code worked the way you were suggesting:

public class Person {
    public string Name { get; set; }
    public decimal Gpa { get; set; }
}

public class Student : Person {
    public new decimal Gpa { get; set; }
}

@HaloFour
Copy link

HaloFour commented Mar 9, 2016

Another would be:

public class Person { }
public class Student : Person { }

public void DoSomething(Person person) { }
public void DoSomething(Student student) { }

Person person = ...;
if (person is Student) {
    DoSomething(person);
}

@Thaina
Copy link

Thaina commented Mar 14, 2016

@HaloFour Then another possiblity is as keyword

object obj;
if(obj as Student && obj.GPA > 3.5) // obj just being student in this block, and if it not it will just be false

Another functionality I would like compiler to have is, if we check null and try to use that object it should throw error. Could this be compiler capability without CLR support?

such as

Student stu = GetStudent("name");
if(stu != null)
{
    //Do something
    return; // Compiler should detect that if stu == null it will never continue
}

if(stu.GPA > 3.5) //Throw error

On the other hand

Student stu = GetStudent("name");
if(stu == null)
{
    stu.GPA = 2; //Obviously error
}

//Do something

@HaloFour
Copy link

@Thaina The as operator returns a reference or null, not a bool, so it couldn't be used directly in a condition like that. Given that pattern matching will already cover these use cases I don't see why another syntax would be necessary.

Non-nullable reference type (#227, #7445) flow analysis may be able to catch the suspicious use of null in your example and issue a warning, assuming GetStudent returns a string?.

@Thaina
Copy link

Thaina commented Mar 14, 2016

@HaloFour We all know that if we use a as type as it would return null. But I think in special case inside if block we could add feature to cast and check at the same time instead of just make it null there which has no meaning in if anyway

If we just if(a as typeB) normally it would be syntax error. I just propose that so we could make it that this syntax will become type transformer like kotln language and it won't break any code already exist

And what I propose about null is simpler than that. I just want compiler checking instead of introduce whole new set of non-nullable reference type feature

We all know that using null anywhere in C# will throw error for sure so if it obvious that any variable is null then it should be compile time error

@gafter
Copy link
Member Author

gafter commented Mar 14, 2016

@Thaina Since the language allows overloading the boolean operators &, and indirectly &&, it is possible your proposal would change the meaning of code such as the following that currently compiles and runs.

    Person obj;
    if(obj as Student && obj.Name == "Thaina")

@bbarry
Copy link

bbarry commented Mar 14, 2016

Student could have an implicit conversion to bool as well couldn't it?

Point is there are lots of nasty little edge cases when it comes to changing semantics of any existing syntactically valid code.

@qwertie
Copy link

qwertie commented Apr 15, 2016

one thing that I don't like about considering tuples in place of out parameters for custom is operators is that will seemingly tie the feature to a frameworks that support System.ValueTuple<...>

I used to deal with that problem often in C# 3.0 + .NET 2.0, by using the LinqBridge library. Similarly, one could define class ExtensionAttribute and be able to use extension methods. If it wants, MS could go a step further and define an "official" forward-compatibility DLL for new features of C# 7, which would eventually become a part of .NET 5; I'm not sure of the technical details but isn't it possible to make such a DLL forward-compatible with .NET 5 programs, in the sense that programs written for 4.6 and 5 would interoperate?

@gafter
Copy link
Member Author

gafter commented Apr 25, 2016

Design notes have been archived at https://github.com/dotnet/roslyn/blob/future/docs/designNotes/2015-11-02%20C%23%20Design%20Demo.md but discussion can continue here.

@gafter gafter closed this as completed Apr 25, 2016
@Thaina
Copy link

Thaina commented May 25, 2016

Just have another idea that would make it work like kotlin

maybe just this syntax

object obj = "";
using(obj as string)
{
    var length = obj.Length; // obj temporarily became string in this block
}

This difference from normal using that it has no = symbol. So it differ from existing code. And if it not contain = symbol then it not dispose, but skip this block if obj as string is null instead

@HaloFour
Copy link

@Thaina

Those scenarios are already handled by proposed type switching:

object obj = "";
if (obj is string s) {
    var length = s.Length;
}

Don't see why there should be yet another way to do this, even if it would avoid a second identifier.

@svick
Copy link
Contributor

svick commented May 25, 2016

@Thaina

So it differ from existing code.

Not really. The following is already valid code (and it works for any type that's implicitly convertible to IDisposable):

object obj =;
using (obj as IDisposable)
{
}

So this would be a breaking change.

@Thaina
Copy link

Thaina commented May 25, 2016

@HaloFour I really hate that syntax to the point that I was create #8004 for going against it

Please, anything but use that syntax

@Thaina
Copy link

Thaina commented May 25, 2016

@svick I forgot it could be used like that because it was state as "not a best practice", and have seen no one use it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests