csharplang icon indicating copy to clipboard operation
csharplang copied to clipboard

Champion "and, or, and not patterns" (VS 16.8, .NET 5)

Open gafter opened this issue 7 years ago • 129 comments

  • [ ] Proposal added
  • [ ] Discussed in LDM
  • [ ] Decision in LDM
  • [ ] Finalized (done, rejected, inactive)
  • [ ] Spec'ed

The idea is to add three new pattern forms

  1. pattern and pattern
  2. pattern or pattern
  3. not pattern

The latter two would not be permitted to define pattern variables in the subpatterns (as they would never be definitely assigned).

We'd have to decide on syntax and precedence, and whether some mechanism such as parentheses could be used for grouping patterns to override precedence.

Examples:

switch (o)
{
    case 1 or 2:
    case Point(0, 0) or null:
    case Point(var x, var y) and var p:
}

gafter avatar Mar 01 '18 16:03 gafter

The latter two would not be permitted to define pattern variables in the subpatterns

For or patterns, as mentioned in https://github.com/dotnet/csharplang/issues/118 we can allow "identical types" for overlapping pattern variables,

switch (o)
{
    // x must be of the same type in both patterns

    case Point(0, var x):
    case Point(var x, 0):
        break;

    case Point(0, var x) or Point(var x, 0):
        break;
}

Alternatively we could use the "most specific common type" for overlapping variables,

switch (o) {
  case A x:
  case B x:
    CommonBase c = x;
    break;
}

but just identical-types would be still useful most of the time as demonstrated by F#.

alrz avatar Mar 01 '18 17:03 alrz

I'm with @alrz, I think that allowing the operands of the or patterns to introduce pattern variables would be a powerful feature with the requirement that the pattern variables must have the same type and must be assigned by both sides.

HaloFour avatar Mar 01 '18 18:03 HaloFour

An alternative to "most specific common type" is the intersection type A | B.

bondsbw avatar Mar 01 '18 23:03 bondsbw

I vote for using &&, ||, ! and not introducing yet more operators that do almost the exact same thing.

MgSam avatar Mar 02 '18 00:03 MgSam

@MgSam

IIRC that would introduce ambiguities when patterns are used as Boolean expressions.

https://github.com/dotnet/roslyn/issues/6235

HaloFour avatar Mar 02 '18 00:03 HaloFour

Are intersection and union types too difficult to implement?

ufcpp avatar Mar 02 '18 01:03 ufcpp

Since the first two join two separate patterns, am I correct in understanding that the following would be valid?:

if (o is IDictionary d and IEnumerable e) {
    // object.ReferenceEquals(d, e) == true
    // (assuming no boxing)
}

if (o is IDictionary and IEnumerable e) {
    // e is IEnumerable only, there is no IDictionary variable created
}

yaakov-h avatar Mar 02 '18 02:03 yaakov-h

@ufcpp How would intersection and union types help with case 1 or 2:?

gafter avatar Mar 02 '18 02:03 gafter

@yaakov-h Regarding your first example, yes. The second example doesn't make sense as IDictionary is not a pattern.

gafter avatar Mar 02 '18 02:03 gafter

Also consider implementing xor patterns (or ^ if one does not like the word xor). It would of course -as with or- not be permitted to define pattern variables.

Unknown6656 avatar Mar 02 '18 20:03 Unknown6656

Also consider implementing xor patterns

Why?

jnm2 avatar Mar 02 '18 21:03 jnm2

@HaloFour What's better? A feature that's ambiguous for the developer or one that's ambiguous for the compiler?

If they can't implement with the existing operators, it's not worth doing. This will just cause endless confusion if added like this. You'll get people trying to write:

if(condition and condition || condition)

More generally, C# team, please stop adding brand new keywords for minor use cases. The in disaster should be enough of a lesson for the reason why.

MgSam avatar Mar 02 '18 21:03 MgSam

@MgSam What's worse is one that is ambiguous for both the developer and the compiler.

gafter avatar Mar 02 '18 21:03 gafter

@MgSam I wouldn't call patterns a "minor use case".

if(condition and condition || condition)

This is not a pattern, or anything else that is or would be accepted by the compiler.

bondsbw avatar Mar 02 '18 23:03 bondsbw

@MgSam You suggestion gives two different contradictory interpretations for this code, one of which is accepted today.

if (myBool is true || false)

gafter avatar Mar 03 '18 00:03 gafter

@jnm2

Why?

~First to be consistent with or and and. Second: You often will find a case where you are semantically asking, whether an object X is either A or B, e.g. when checking for numeric non-continuous ranges, discriminated unions. ...~

Forget it - xor is a bad idea.

Unknown6656 avatar Mar 03 '18 08:03 Unknown6656

With and-patterns and or-patterns we will finally have a way of comparing a variable against a given list of multiple values, just as every newbie programmer wants to do in any language :grin:

if(x is 1 or 2 or 3)
{
    // Hooray!
}

Richiban avatar Mar 07 '18 11:03 Richiban

what if we add this in the LHS of the is operator 🤔

if (x and y is not null)

alrz avatar Mar 07 '18 11:03 alrz

@Richiban newbie may do with a IEnumerable (Here i use an array). (new []{1,2,3}).Contains(x)

GeraudFabien avatar Mar 07 '18 13:03 GeraudFabien

@alrz

if (x and y are not null)

FTFY. :wink:

bondsbw avatar Mar 07 '18 16:03 bondsbw

if (neither x nor y are null)

😁

jnm2 avatar Mar 07 '18 16:03 jnm2

This is too funny please staph

Thaina avatar Mar 08 '18 06:03 Thaina

@jmn2: Looks suspiciously like VB.NET...... (.__.)

If Neither x Nor y Are Nothing Then
    ' Just kiddin' - although it would funny to see,
    ' whether the VB compiler accepts Shakespeare's works as valid code
End If

Unknown6656 avatar Mar 08 '18 19:03 Unknown6656

(x, y) = default; still doesn't seem to work. 😢

jnm2 avatar Apr 30 '18 15:04 jnm2

@jnm2 We'll have to wait for 8.0 (https://github.com/dotnet/csharplang/issues/1394)

alrz avatar Apr 30 '18 15:04 alrz

All of this can be done with some creativity. Newbs should be learning how to do things like this with what the language already has.

geniusFunk avatar May 02 '18 02:05 geniusFunk

As no-one's mentioned this yet: this is particularly important for switch expressions. Consider this existing code:

public int Foo(int input)
{
    switch (input)
    {
        case 0:
        case 1:
            return SomeMethod1() + SomeMethod2();
        case 2:
            return -1;
        default:
            return input;
    }
}

This looks like it should be refactored to an expression-bodied method with a switch expression, but until the feature of this issue is implemented, that can't be cleanly done without either a guard clause or code duplication:

// Duplication approach
public int Foo(int input) => input switch
    {
        0 => SomeMethod1() + SomeMethod2(),
        1 => SomeMethod1() + SomeMethod2(),
        2 => -1,
        _ => input
    };

// Guard clause approach (which I suspect will be less efficient)
public int Foo(int input) => input switch
    {
        _ when input == 0 || input == 1 => SomeMethod1() + SomeMethod2(),
        2 => -1,
        _ => input
    };

If this feature is adopted, or something similar, we get the much more pleasant:

public int Foo(int input) => input switch
    {
        0 or 1 => SomeMethod1() + SomeMethod2(),
        2 => -1,
        _ => input
    };

jskeet avatar May 12 '18 09:05 jskeet

It looks like this was briefly discussed in LDM: https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-03-19.md#and-or-and-not-patterns-1350

If this is outside the scope for C# 8.0, will we get the in pattern instead or some other way of matching multiple patterns inside a switch expression? If not, that could be a significant limitation forcing us to keep using the switch statement in many cases.

Neme12 avatar May 12 '18 09:05 Neme12

Is this really important? What are the benefits offered by this compared to the standard operators (||, &&, and !)?

Truerror avatar May 13 '18 11:05 Truerror

@Lifeburner: Those operators don't work to compose patterns, and couldn't do so without more work, as per Neal's if (myBool is true || false) example. Fundamentally this is composing patterns rather than values, which I think is an important distinction.

jskeet avatar May 13 '18 16:05 jskeet