From 36bc9d56c48812072b7d2e91246f45fddf7245d2 Mon Sep 17 00:00:00 2001 From: Omar Tawfik Date: Fri, 13 Apr 2018 12:17:11 -0700 Subject: [PATCH 1/4] Added ranges proposal --- proposals/csharp-8.0/ranges.md | 58 ++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 proposals/csharp-8.0/ranges.md diff --git a/proposals/csharp-8.0/ranges.md b/proposals/csharp-8.0/ranges.md new file mode 100644 index 0000000..e616ca7 --- /dev/null +++ b/proposals/csharp-8.0/ranges.md @@ -0,0 +1,58 @@ +# Ranges + +* [x] Proposed +* [ ] Prototype: Not Started +* [ ] Implementation: Not Started +* [ ] Specification: Not Started + +## Summary + +This feature is about delivering two new operators that allow constructing `System.Index` and `System.Range` objects, and using them to index/slice collections at runtime. + +## Detailed Design + +#### System.Index + +C# has no way of indexing a collection from the end, but rather most indexers use the "from start" notion, or do a "length - i" expression. We introduce a new Index expression that means "from the end". The feature will introduce a new unary prefix "hat" operator. Its single operand must be convertible to `System.Int32`. It will be lowered into the appropriate `System.Index` factory method call. + +```csharp +var thirdItem = list[2]; // list[2] +var lastItem = list[^1]; // list[Index.CreateFromEnd(1)] + +var multiDimensional = list[3, ^2] // list[3, Index.CreateFromEnd(2)] +``` + +#### System.Range + +C# has no syntactical way to access "ranges" or "slices" of collections. Usually users are forced to implement complex structures to filter/operate on slices of memory, or resort to LINQ methods like `list.Skip(5).Take(2)`. With the addition of `System.Span` and other similar types, it becomes more important to have this kind of operations supported on a deeper level in the language/runtime, and have the interface unified. + +The language will introduce a new range operator `x..y`. It is a binary infix operator that accepts two expressions. Either operands can be ommited (examples below), and they have to be convertible to `System.Index`. It will be lowered to the appropriate `System.Range` factory method call. + +```csharp +var slice1 = list[2..^3]; // list[Range.Create(2, Index.CreateFromEnd(3))] +var slice2 = list[..^3]; // list[Range.ToEnd(Index.CreateFromEnd(3))] +var slice3 = list[2..]; // list[Range.FromStart(2)] +var slice4 = list[..]; // list[Range.All] + +var multiDimensional = list[1..2, ..] // list[Range.Create(1, 2), Range.All] +``` + +## Workarounds + +For prototyping reasons, and since runtime/framework collections will not have support for such indexers, the compiler will finally look for the following extension method when doing overload resolution: + +* `op_Indexer_Extension(this TCollection collection, ...arguments supplied to the indexer)` + +This workaround will be removed once contract with runtime/framework is finalized, and before the feature is declared complete. + +Moreover, `System.Index` should have an implicit conversion from `System.Int32`, in order not to need to overload for mixing integers and indexes over multi-dimensional signatures. + +## Alternatives + +If we decide not to implement the feature (which is majorly a syntactic sugar), developers can still define such interfaces/operations explicitly, but it will result in a lot more boilerplate code that can be avoided, and harder to unify types/experience across the many collection types in .NET. + +## IL Representation + +These two operators will be lowered to regular indexer/method calls, with no change in subsequent compiler layers. + +## Questions From b6e53e8db2a225db1213d53a94df94c2273420d6 Mon Sep 17 00:00:00 2001 From: Omar Tawfik Date: Fri, 13 Apr 2018 14:47:05 -0700 Subject: [PATCH 2/4] PR Comments --- proposals/csharp-8.0/ranges.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/proposals/csharp-8.0/ranges.md b/proposals/csharp-8.0/ranges.md index e616ca7..89feb07 100644 --- a/proposals/csharp-8.0/ranges.md +++ b/proposals/csharp-8.0/ranges.md @@ -24,9 +24,9 @@ var multiDimensional = list[3, ^2] // list[3, Index.CreateFromEnd(2)] #### System.Range -C# has no syntactical way to access "ranges" or "slices" of collections. Usually users are forced to implement complex structures to filter/operate on slices of memory, or resort to LINQ methods like `list.Skip(5).Take(2)`. With the addition of `System.Span` and other similar types, it becomes more important to have this kind of operations supported on a deeper level in the language/runtime, and have the interface unified. +C# has no syntactical way to access "ranges" or "slices" of collections. Usually users are forced to implement complex structures to filter/operate on slices of memory, or resort to LINQ methods like `list.Skip(5).Take(2)`. With the addition of `System.Span` and other similar types, it becomes more important to have this kind of operation supported on a deeper level in the language/runtime, and have the interface unified. -The language will introduce a new range operator `x..y`. It is a binary infix operator that accepts two expressions. Either operands can be ommited (examples below), and they have to be convertible to `System.Index`. It will be lowered to the appropriate `System.Range` factory method call. +The language will introduce a new range operator `x..y`. It is a binary infix operator that accepts two expressions. Either operands can be omitted (examples below), and they have to be convertible to `System.Index`. It will be lowered to the appropriate `System.Range` factory method call. ```csharp var slice1 = list[2..^3]; // list[Range.Create(2, Index.CreateFromEnd(3))] @@ -49,7 +49,7 @@ Moreover, `System.Index` should have an implicit conversion from `System.Int32`, ## Alternatives -If we decide not to implement the feature (which is majorly a syntactic sugar), developers can still define such interfaces/operations explicitly, but it will result in a lot more boilerplate code that can be avoided, and harder to unify types/experience across the many collection types in .NET. +The new operators (`^` and `..`) are syntactic sugar. The functionality can be implemented by explicit calls to `System.Index` and `System.Range` factory methods, but it will result in a lot more boilerplate code, and the experience will be unintuitive. ## IL Representation From 3945f6c228358c8dc9d66aa8ef289804c77332ee Mon Sep 17 00:00:00 2001 From: Omar Tawfik Date: Tue, 17 Apr 2018 12:05:59 -0700 Subject: [PATCH 3/4] More PR Comments --- proposals/{csharp-8.0 => }/ranges.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) rename proposals/{csharp-8.0 => }/ranges.md (82%) diff --git a/proposals/csharp-8.0/ranges.md b/proposals/ranges.md similarity index 82% rename from proposals/csharp-8.0/ranges.md rename to proposals/ranges.md index 89feb07..689034b 100644 --- a/proposals/csharp-8.0/ranges.md +++ b/proposals/ranges.md @@ -1,10 +1,5 @@ # Ranges -* [x] Proposed -* [ ] Prototype: Not Started -* [ ] Implementation: Not Started -* [ ] Specification: Not Started - ## Summary This feature is about delivering two new operators that allow constructing `System.Index` and `System.Range` objects, and using them to index/slice collections at runtime. @@ -24,7 +19,7 @@ var multiDimensional = list[3, ^2] // list[3, Index.CreateFromEnd(2)] #### System.Range -C# has no syntactical way to access "ranges" or "slices" of collections. Usually users are forced to implement complex structures to filter/operate on slices of memory, or resort to LINQ methods like `list.Skip(5).Take(2)`. With the addition of `System.Span` and other similar types, it becomes more important to have this kind of operation supported on a deeper level in the language/runtime, and have the interface unified. +C# has no syntactic way to access "ranges" or "slices" of collections. Usually users are forced to implement complex structures to filter/operate on slices of memory, or resort to LINQ methods like `list.Skip(5).Take(2)`. With the addition of `System.Span` and other similar types, it becomes more important to have this kind of operation supported on a deeper level in the language/runtime, and have the interface unified. The language will introduce a new range operator `x..y`. It is a binary infix operator that accepts two expressions. Either operands can be omitted (examples below), and they have to be convertible to `System.Index`. It will be lowered to the appropriate `System.Range` factory method call. @@ -56,3 +51,5 @@ The new operators (`^` and `..`) are syntactic sugar. The functionality can be i These two operators will be lowered to regular indexer/method calls, with no change in subsequent compiler layers. ## Questions + +* Should `System.Index` throw when constructed with negative values? From 7c8a788cb9b558dea69ede7faf4ffed1ae181348 Mon Sep 17 00:00:00 2001 From: Omar Tawfik Date: Thu, 19 Apr 2018 13:44:50 -0700 Subject: [PATCH 4/4] More comments --- proposals/ranges.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/proposals/ranges.md b/proposals/ranges.md index 689034b..c6b1711 100644 --- a/proposals/ranges.md +++ b/proposals/ranges.md @@ -32,6 +32,8 @@ var slice4 = list[..]; // list[Range.All] var multiDimensional = list[1..2, ..] // list[Range.Create(1, 2), Range.All] ``` +Moreover, `System.Index` should have an implicit conversion from `System.Int32`, in order not to need to overload for mixing integers and indexes over multi-dimensional signatures. + ## Workarounds For prototyping reasons, and since runtime/framework collections will not have support for such indexers, the compiler will finally look for the following extension method when doing overload resolution: @@ -40,8 +42,6 @@ For prototyping reasons, and since runtime/framework collections will not have s This workaround will be removed once contract with runtime/framework is finalized, and before the feature is declared complete. -Moreover, `System.Index` should have an implicit conversion from `System.Int32`, in order not to need to overload for mixing integers and indexes over multi-dimensional signatures. - ## Alternatives The new operators (`^` and `..`) are syntactic sugar. The functionality can be implemented by explicit calls to `System.Index` and `System.Range` factory methods, but it will result in a lot more boilerplate code, and the experience will be unintuitive. @@ -50,6 +50,11 @@ The new operators (`^` and `..`) are syntactic sugar. The functionality can be i These two operators will be lowered to regular indexer/method calls, with no change in subsequent compiler layers. -## Questions +## Runtime behaviour -* Should `System.Index` throw when constructed with negative values? +* Compiler can optimize indexers for built-in types like arrays and strings, and lower the indexing to the appropriate existing methods. +* `System.Index` will throw if constructed with a negative value. +* `^0` does not throw, but it translates to the length of the collection/enumerable it is supplied to. +* `Range.All` is semantically equivalent to `0..^0`, and can be deconstructed to these indices. + +## Questions