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

Proposal: list pattern #3435

Open
Tracked by #829
gafter opened this issue May 7, 2020 · 68 comments
Open
Tracked by #829

Proposal: list pattern #3435

gafter opened this issue May 7, 2020 · 68 comments
Assignees
Labels
Implemented Needs ECMA Spec This feature has been implemented in C#, but still needs to be merged into the ECMA specification Proposal champion Proposal
Milestone

Comments

@gafter
Copy link
Member

gafter commented May 7, 2020

Allow is [ 1, 2, 3 ] (list pattern), is [length] (length pattern) and is [ 1, ..var x, 5 ] (slice pattern).

Speclet: https://github.com/dotnet/csharplang/blob/main/proposals/csharp-11.0/list-patterns.md

See #3245

LDM Discussions:

@gafter gafter self-assigned this May 7, 2020
@HaloFour
Copy link
Contributor

HaloFour commented May 7, 2020

Awesome. Is this going to build on the linked proposal (with your additional feedback) or is this to track the work of designing list/collection/dictionary/etc patterns in general?

@gafter
Copy link
Member Author

gafter commented May 7, 2020

@HaloFour This is to track answering questions like those.

@alrz
Copy link
Contributor

alrz commented May 9, 2020

list/collection/dictionary/etc patterns

I wonder how you'd imagine dictionary/indexer patterns work and what use cases you have in mind.

Syntax-wise it could be {[constant]: pattern} but that's useless because any common type that exposes an indexer, throws if it's out of range.

@gafter
Copy link
Member Author

gafter commented May 10, 2020

Maybe we could use a TryGet method.

@alrz
Copy link
Contributor

alrz commented May 10, 2020

To me, that's too specific to have a dedicated syntax for. I think we should enable patterns for all Try-like methods which is essentially what user-defined positional patterns are.

map is TryGetValue("constant key", pattern) // maybe with another name

This could made to work only on "some" methods as mentioned in #1047 (comment)

Syntactical symmetry with indexer initializers makes it attractive though, however, the fact that it would not call the indexer might be confusing.

@333fred 333fred added this to the 10.0 candidate milestone Jul 13, 2020
@333fred 333fred modified the milestones: 10.0 candidate, 10.0 Working Set Sep 28, 2020
@HurricanKai
Copy link
Member

Would this allow something like

Array: { Length: 1, [1] }

checking whether there is only one element and whether that element is 1?
Also, looking at the PR it's unclear to me whether this would allow checking for just the 5th element for example? Looks like that's not possible?

@alrz
Copy link
Contributor

alrz commented Oct 26, 2020

@HurricanKai

The pattern [1] already includes the check for length=1 so you don't need to add anything else.

To match Nth element you could prepend N-1 discards - it tends to get long but it is definitely possible.

The team is interested in supporting indexers, so using range indexer pattern, we could support { [5]: var elem } and generate e.Length >= 5 && e[5] is var elem (we'd only accept constant indexer args so we can calculate the minimum size).

I'm not sure if that's common enough for a dedicated support, plus it won't work with generic dictionaries which is probably a deal breaker.

@HurricanKai
Copy link
Member

I'd generally much prefer [idx]: { ... } because it looks much more natural inside of more pattern matching, in fact I tried typing it out like that today cause I thought it would work 😄 ie [2]: { [5]: { Length: 5 } } on a string[][] would be quite amazing if possible.

@Unknown6656
Copy link
Contributor

Unknown6656 commented May 21, 2021

Does this proposal also include list decomposition?
e.g. (syntax debatable):

List<int> my_list = .....;

{ int first, int second, .., int last } = my_list;

@333fred
Copy link
Member

333fred commented May 21, 2021

No. That's #4082.

@CyrusNajmabadi
Copy link
Member

@jcouv We've been hearing quite a bit about this from several community channels (twitter, discord, etc.) Can we bring this to an upcoming meeting to discuss and make some decisions on? Thanks!

@jcouv
Copy link
Member

jcouv commented Jun 2, 2021

@CyrusNajmabadi Will do.

@mungojam
Copy link

mungojam commented Jun 21, 2021

reading the design meeting notes, if the eventual outcome is that we can also replace:

var things = new [] { "Car", "Motorbike", "Cab" };

with

var things = ["Car", "Motorbike", "Cab"];

I'll be very pleased. It's one of the things that jars when I work with 3 different languages in my day job (C#, Typescript/json and Python)

@emperador-ming
Copy link

This is awesome. It would be great and more succinct if 'var' keyword were optional inside brackets.

int Add(List<int> list) list switch 
{
   [] => 0, 
   [head, ..tail] => head + Add(tail) 
};

@HaloFour
Copy link
Contributor

HaloFour commented Jun 2, 2022

@emperador-ming

That's a normal subpattern, it's not special for list patterns. C# always requires var or the type name for variable patterns and that is intentional.

@emperador-ming
Copy link

@emperador-ming

That's a normal subpattern, it's not special for list patterns. C# always requires var or the type name for variable patterns and that is intentional.

Quite verbose.

@CyrusNajmabadi
Copy link
Member

Quite verbose.

Your code already has meaning :) head may be a constant which means the list that starts with that constant value.

@theunrepentantgeek
Copy link

Quite verbose

Very often opposite of verbose is not concise, but cryptic.

Nobody wants things to be more verbose than needed, but trying to go further than that just results in obfuscated Code that's needlessly difficult to read and maintain.

@FaustVX
Copy link

FaustVX commented Jun 2, 2022

@emperador-ming

It would be great and more succinct if 'var' keyword were optional inside brackets.

The var keyword (or typename) is essential in C#, var (or typename) before an identifier means we declare a new variable.
Without it, no one can know a new variable is declared

@emperador-ming
Copy link

emperador-ming commented Jun 2, 2022

Being list-patterns a typical functional paradigm I'm just proposing what is common in functional languages. I fail to see in what manner, in this precise context, making var optional obfuscates code.

@CyrusNajmabadi
Copy link
Member

I fail to see in what manner, in this precise context, making var optional obfuscates code.

@emperador-ming As i mentioned above, that code already has meaning. It means "match if the list contains this value as the first element in it". Thanks! :)

@HaloFour
Copy link
Contributor

HaloFour commented Jun 2, 2022

@emperador-ming

Being list-patterns a typical functional paradigm I'm just proposing what is common in functional languages. I fail to see in what manner, in this precise context, making var optional obfuscates code.

What is good for other languages isn't necessarily good for C#. As mentioned, this has nothing to do with list patterns. That is just a normal subpattern, and variable subpatterns always require the typename or var. That is how variables are declared in C# and that intent and consistency is important.

@emperador-ming
Copy link

@emperador-ming

It would be great and more succinct if 'var' keyword were optional inside brackets.

The var keyword (or typename) is essential in C#, var (or typename) before an identifier means we declare a new variable. Without it, no one can know a new variable is declared

Correct me if I'm wrong, but in this context these variables don't vary at all. They're just expressions.

@HaloFour
Copy link
Contributor

HaloFour commented Jun 2, 2022

@emperador-ming

Correct me if I'm wrong, but in this context these variables don't vary at all. They're just expressions.

Variable patterns introduce normal variables. They can vary in that variables in C# are mutable.

@jnm2
Copy link
Contributor

jnm2 commented Jun 3, 2022

If var was not required, then this would not be a normal subpattern and presumably you couldn't do useful things like this:

if (buttons is [{ Tag: SomeConstant }])

@emperador-ming
Copy link

Even if it's optional?

@FaustVX
Copy link

FaustVX commented Jun 3, 2022

if (buttons is [{ Tag: var someVar}])

This means: Store the Tag property of the first element of button inside the variable someVar.
Then later, you could alter that value if you want.

@ufcpp
Copy link

ufcpp commented Jul 19, 2022

Can we combine collection expression and u8 literal?
For instance, sometimes I write the following kind of code:

var dat = new byte[]
{
    130, // Map 2

    161, 65, // "A"
    171, 83, 111, 109, 101, 32, 83, 116, 114, 105, 110, 103, // "Some String"

    161, 66, // "B"
    174, 65, 110, 111, 116, 104, 101, 114, 32, 83, 116, 114, 105, 110, 103, // "Another String"
};

// <PackageReference Include="MessagePack" Version="2.3.85" /> in the csproj
var json = MessagePack.MessagePackSerializer.ConvertToJson(dat);

Console.WriteLine(json); // {"A":"Some String","B":"Another String"}

And I'd like to rewrite this as follows:

var dat = new byte[]
{
    130, // Map 2
    161, .."A"u8,
    171, .."Some String"u8,
    161, .."B"u8,
    174, .."Another String"u8,
};

@jnm2
Copy link
Contributor

jnm2 commented Jul 19, 2022

@ufcpp I think this would be a scenario to bring up within #5354.

@CyrusNajmabadi
Copy link
Member

i think that would work...

@CyrusNajmabadi
Copy link
Member

CyrusNajmabadi commented Jul 19, 2022

Here's why, as per the rules we came up with.

  1. you're making an array. That means we need to know the length up front.
  2. We determine the length from the count of all elements provided, plus the sum of hte lengths of all the splats (which we require be known).
  3. All these splats have known lengths and i believe shoudl be splattable types.
  4. So we will be able to figure out the final length of hte array, and add all the elements to it.

@qrli
Copy link

qrli commented Jul 25, 2022

Is that really related to list pattern? I think it is all about splat operator.
As for splat operator, not only literals, variables should work too, IMO.

@jnm2
Copy link
Contributor

jnm2 commented Jul 25, 2022

It is not, and that's why I suggested having the conversation at #5354.

@ufcpp
Copy link

ufcpp commented Jul 25, 2022

Yes, but I already have the right answer: #3435 (comment)

@mpawelski
Copy link

So what's the current stance of lang team about changing .. to ..., and renaming "slice pattern" to "spread pattern" as I mentioned in this comment?

Looks like C# 11 is getting finalized and we stayed with .. "slice syntax". For example it was mentioned in this recent talk by @MadsTorgersen

@CyrusNajmabadi
Copy link
Member

@mpawelski there is no change to the design coming.

@333fred
Copy link
Member

333fred commented Sep 8, 2022

That was discussed in LDM here: https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-03-09.md#ambiguity-of--in-collection-expressions.

@jcouv jcouv modified the milestones: Working Set, 11.0 Sep 26, 2022
@jnyrup
Copy link
Contributor

jnyrup commented Jan 8, 2023

https://github.com/dotnet/csharplang/blob/main/proposals/csharp-11.0/list-patterns.md#lowering specifies that

A pattern of the form expr is [1, 2, 3] is equivalent to the following code:

expr.Length is 3
&& expr[new Index(0, fromEnd: false)] is 1
&& expr[new Index(1, fromEnd: false)] is 2
&& expr[new Index(2, fromEnd: false)] is 3

When trying out the same code on SharpLab it generates the code below which includes a null check on expr.

public bool M(int[] expr)
{
    if (expr != null && expr.Length == 3 && expr[0] == 1 && expr[1] == 2)
    {
        return expr[2] == 3;
    }
    return false;
}

Is the compiler over-implementing list patterns by always including a null check, or is the language construct under-specified and should say that the compiler must emit a null check if it cannot prove expr to be non-null?

It might be that I'm simply misunderstanding what "equivalent" means here, i.e. how much the compiler can diverge from the specification.

@CyrusNajmabadi
Copy link
Member

The spec is incorrect. It should say there is a null check.

@333fred 333fred added the Implemented Needs ECMA Spec This feature has been implemented in C#, but still needs to be merged into the ECMA specification label Jan 9, 2023
@jcouv jcouv added the Proposal label Sep 17, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Implemented Needs ECMA Spec This feature has been implemented in C#, but still needs to be merged into the ECMA specification Proposal champion Proposal
Projects
None yet
Development

No branches or pull requests