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

Pattern-matching versus arrays, dictionary, list, dynamic? #10631

Closed
3 tasks
gafter opened this issue Apr 15, 2016 · 7 comments
Closed
3 tasks

Pattern-matching versus arrays, dictionary, list, dynamic? #10631

gafter opened this issue Apr 15, 2016 · 7 comments

Comments

@gafter
Copy link
Member

gafter commented Apr 15, 2016

  • Will we support pattern matching with arrays?
  • Will we support pattern matching with List, Dictionary<K, V>? Think particularly of F#'s really useful list deconstruction, which feels impossible with IEnumerable since patterns shouldn't generally be executing methods like GetEnumerator/MoveNext.
  • How should the type dynamic interact with pattern matching?
@HaloFour
Copy link

Is there an issue using array/collection initializer syntax for list patterns that would apply to anything IList<T>, including arrays?

int[] array = new [] { 1, 2, 3 };
var list = new List<int>() { 1, 2, 3 };

let { 1, var second, 2 } = array else return;
let { 1, 2, var third } = list else return;

A sequence wildcard pattern would be nice to indicate that you don't care how many previous or subsequent elements there might be:

var list = GetListOfNumbers();
let { var first, **, var last } = list else return;

F# has no such facility.

The cons pattern is interesting, but I'm not aware of a CLR type that it would map to cleanly. IIRC in functional languages it always is used to construct/deconstruct an immutable linked list. IEnumerable<T> is academically interesting but probably impractical.

As for dynamic, I don't think that the current implementation provides a way to inspect truly dynamic types? Otherwise, I'd try to support the gamut of patterns through emitting code that inspects the dynamic type for the appropriate shape.

dynamic expando = new ExpandoObject();
expando.Foo = 123;
expando.Bar = "456";

switch (expando) {
    case { Foo is 123, Bar is "456" }:
        Console.WriteLine("matched!");
        break;
}

@orthoxerox
Copy link
Contributor

As I have said in one of the other issues, I feel that arrays/lists will be well-served by a cons pattern coupled with list views/slices/segments. Enumerables are better off with LINQ. Dynamics can be matched against a property pattern. I have nothing to offer wrt dictionaries.

@alrz
Copy link
Contributor

alrz commented Apr 17, 2016

I think complete analogy with object-or-collection-initializer would be really neat and I don't think it would be ambiguous at all (that's why we have a non-assignment-expression in collection initializers).

And for dictionaries we can use indexer patterns (#10600 (comment)).

@JesperTreetop
Copy link

JesperTreetop commented Apr 17, 2016

Is there an issue using array/collection initializer syntax for list patterns that would apply to anything IList, including arrays?

If possible, please make that some sort of duck-typed IReadOnlyList<T> instead - IEnumerable<T> with an indexer with a getter of T and a long or int Count. (In the tradition of awaitables, foreach and collection initializers being speced using duck typing instead of interfaces. If IReadOnlyList<T> had been there right in 2.0, everyone would have adopted it and this wouldn't have been necessary.)

A sequence wildcard pattern would be nice to indicate that you don't care how many previous or subsequent elements there might be:

I propose using var first, ..., var last instead, which conveys more "multi-elemental-ness" (it is literally the yada-yada-yada operator in Perl) than just repeating the *. Or why not var first, params, var last? (Just kidding. 😜)

For dictionaries, how about:

var d = new Dictionary<string, int> {
    ["trite"] = 42
};
switch (d) {
    case var answer = ["trite"]:
        // ...
}

var elaborate = new Dictionary<string, string {
    ["type"] = "string",
    ["integer"] = null,
    ["string"] = "xyz"
};
switch (elaborate) {
    // I am unsure about whether the following syntax is enabled by recursive patterns
    case var value = [var type = ["type"]]:
        // ...
}

To avoid exceptions when getting things that are simply not there, this would use, in order of availability:

  • TryGetValue(TKey, out TValue) (implementable without being a dictionary, again like Add in collection initializers, would be picked up in IReadOnlyDictionary<TKey, TValue> and Dictionary<TKey, TValue>)
  • Maybe allowable: Contains(TKey) followed by this[TKey] => TValue; not atomic and could throw during races, but not more or less dangerous than just writing that code outside of a pattern. Limited to dictionaries (like non-generic IDictionary and Hashtable) that don't implement TryGetValue.
  • Very probably not allowable: just this[key], but throw if the indexer did

The above pattern could also be adapted to random access list/collection index matching.

@gafter gafter modified the milestones: 2.0 (RC), 3.0 Apr 27, 2016
@Richiban
Copy link

Richiban commented Aug 3, 2016

Pattern matching with arrays & lists will be a bit difficult since C# (still) doesn't have array & list literals!

Normally pattern matching is understandable because the syntax to deconstruct and to construct are the same. Consider tuples: var (x, y) = (1, 2);. There is a pleasing, easy-to-understand symmetry to this form, thanks to the tuple literal. How would it work with arrays?

let (new [] { var first }) = new [] {1, 2, 3} else return;

This has symmetry but looks really awkward... Any chance of C# finally having real, honest to god list and array literals? I'm going to completely rip off F# here with a suggestion:

let myList = [1, 2, 3];

switch (myList)
{
    case [var first, **] : return first;
    case [] : throw new InvalidOperationException("Cannot get first item of empty list");
}

And for arrays:

let myArray = [| 'a', 'b', 'c' |];

switch (myArray)
{
    case [| var first, ** |] : return first;
    case [| |] : throw new InvalidOperationException("Cannot get first item of empty array");
}

While the cons idea seems right at first glance, I don't think it's right for C# since List is not a singly-linked list that can easily be pushed and popped like a stack.

IEnumerable is not appropriate for deconstructing because, as hinted at above, you're quite possibly causing side effects or causing re-evaluation of some sequence by walking the IEnumerable.

@Richiban
Copy link

Richiban commented Aug 3, 2016

Actually, a better idea might be to support some duck typing here.

The case [var first, var second] pattern can be used to deconstruct any type that supports an int-indexer, i.e. implements this[int index]. Perhaps the type should also implement Count since case [var first, var second] technically will only match a list of length 2;

@gafter
Copy link
Member Author

gafter commented Sep 11, 2017

Issue moved to dotnet/csharplang #898 via ZenHub

@gafter gafter closed this as completed Sep 11, 2017
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

6 participants