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

Notes on Records and Pattern Matching for 2015-10-07 design review #5757

Closed
gafter opened this issue Oct 7, 2015 · 8 comments
Closed

Notes on Records and Pattern Matching for 2015-10-07 design review #5757

gafter opened this issue Oct 7, 2015 · 8 comments

Comments

@gafter
Copy link
Member

gafter commented Oct 7, 2015

Records and Pattern Matching (#206)

Prototyped together last year by @semihokur, but that prototype is
based on very out-of-date Roslyn code. We also have some design changes
since that time and we want to separate a pattern-matching prototype from
records/ADTs so we can make independent decisions about whether
and when to include them in the language(s).

First step is to port pattern matching to the latest sources.
In-progress port at #4882

Spec changes since the 2014 prototype

For pattern matching:

  1. Scoping of pattern-introduced variables (with "funny" rule for if)
  2. Rules for switch statement that make it a compatible extension of the existing construct (Discussion: pattern matching versus switch statement #4944)
  3. An expression form of multi-arm pattern-matching (Proposal: expression-based switch ("match") for pattern matching #5154)
  4. A when clause added to switch cases.

And, for records:

  1. No record keyword necessary
  2. with expressions (Proposal: "with" expressions for record types #5172)
  3. Approach for for algebraic data types

Implementation status of prototype port

  1. For pattern matching, checklist at Pattern Matching #4882 tracking the progress
  2. For records, port not started

Making the extension of switch backward-compatible

  • We say that the cases are matched in order, except default which is always the last
    resort.
  • Integral-typed case labels match any integral-valued control expression with the same value.
  • One issue around user-defined conversions to switchable types is
    resolved (Discussion: pattern matching versus switch statement #4944). In the draft spec,
    a conversion will be applied on the cases, not on the control-expression unilaterally.
    Instead of converting only to swithable types, each
    case arm will consider any conversions that allow the case to be applied.
    Any given conversion would be applied at most once.
Foo foo = ...; // has a conversion to int
switch (foo)
{
    case 1: // uses the converted value
    case Foo(2): // uses the original value
    case 3: // uses the converted value
}
  • The goto case statement is extended to allow any expression as its argument.

Expression form of multi-arm pattern matching (#5154)

var areas =
    from primitive in primitives
    let area = primitive match (
        case Line l: 0
        case Rectangle r: r.Width * r.Height
        case Circle c: Math.PI * c.Radius * c.Radius
        case *: throw new ApplicationException()
    )
    select new { Primitive = primitive, Area = area };

There is no default here, so cases are handled strictly in order.

I propose the spec require that the compiler "prove" that all cases are handled
in a match expression using not-yet-specified rules. Writing those rules
is an open work item, but I imagine it will require the compiler to build
a decision tree and check it for completeness. That will also be needed to
implement checks that no case is subsumed by a previous case, which will
cause a warning (for switch) or error (for match).

With-expressions (#5172)

class Point(int X, int Y, int Z);
...
    Point p = ...;
    Point q = p with { Y = 2 };

The latter is translated into

    Point q = new Point(X: p.X, Y: 2, Z: p.Z);

We know how to do this for record types (because the language specifies the
mapping between constructor parameters and properties). We're examining how
to extend it to more general types.

To support inheritance, rather than directly using the constructor (as above) the generated code will
invoke a compiler-generated (but user-overridable) factory method.

    Point q = p.With(X: p.X, Y: 2, Z: p.Z);

Draft approach for algebraic data types

abstract sealed class Expression
{
    class Binary(Operator Operator, Expression Left, Expression Right) : Expression;
    class Constant(int Value) : Expression;
}

None of these classes would be permitted to be extended elsewhere.
a match expression that handles both Binary and Constant cases
would not need a * (default) case, as the compiler can prove it
is complete.

Remaining major issues

  1. We need to specify the rules for checking
    • If the set of cases in a match is complete
    • If a case is subsumed by a previous case
  2. We need more experience with algebraic data types and active patterns.
  3. Can we extend with expressions to non-record types?
@gafter
Copy link
Member Author

gafter commented Oct 7, 2015

This discussion will be moved to next month's design review because I'm home sick today.

@alrz
Copy link
Member

alrz commented Oct 8, 2015

Please consider these features as well:

  • Access modifiers on record members
  • Mutable record members

Will record types be solely immutable? I think the ability to define EF entity classes or attributes as record types are reasonable enough to make record types not-solely-immutable. Even F# as a functional language has CLIMutableAttribute to make the former possible. However, ADTs would remain totally immutable (see #206 comment).

@gafter
Copy link
Member Author

gafter commented Oct 8, 2015

@alrz Record members can be mutable, but they are currently required to be public. See section 1.1.2 in #206.

@alrz
Copy link
Member

alrz commented Nov 3, 2015

@gafter So, there would be a mutable keyword?

@bbarry
Copy link

bbarry commented Nov 3, 2015

@alrz

public class MutablePoint(int X, int Y) {
    public int X { get; set; }
    public int Y { get; set; }
}

To make the record class mutable you define the properties with get and set.

@alrz
Copy link
Member

alrz commented Nov 3, 2015

I was thinking about a more concise syntax like class MutablePoint(mutable int X, mutable int Y);

@gafter
Copy link
Member Author

gafter commented Nov 3, 2015

@alrz Pattern matching is part of a set of features intended to make safer things easier to do. Mutation is not one of the safer things we are trying to make easier.

@gafter
Copy link
Member Author

gafter commented Apr 25, 2016

Design notest have been archived at https://github.com/dotnet/roslyn/blob/future/docs/designNotes/2015-10-07%20C%23%20Design%20Review%20Notes.md but discussion can continue here.

@gafter gafter closed this as completed Apr 25, 2016
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

3 participants