Open xperiandri opened 1 year ago
I know GetSlice is kind of fresh, is there a sense that now "the BCL team has decided the name", we could deprecate GetSlice
through a warning, and have Slice
instead?
It seems the "framework design guidelines" trend of everything being a bit verbose and explicit, "GetX", are past history, and the BCL design team is looking at C++ (https://en.cppreference.com/w/cpp/numeric/valarray/slice) for choosing identifiers more than those original guidelines.
Maybe that will help us making the same, if we face again "let's do transversal feature that carries a name into F# libraries" but can't wait for BCL team to decide, we can pick from C++ if the concept exists there and is applicable.
I run into this most frequently with Span
.
Is there a good reason not to add support for Slice
?
The only downsides I can think of are:
GetSlice
if both it and Slice
existed and were in scope.But otherwise it seems silly to make everyone write an extension method to use the BCL collections' built-in slicing capabilities, forever, when the compiler could just do it for them.
I'll mark this as approved in principle. It should be possible define Slice and have the F# slicing syntax light up.
Equally the SRTP workaround is pretty good!
So it turns out that this is closely related to #1044, because the C# spec for slicing and ranges follows these rules:
System.Range
; if found, use that.Slice(int, int)
method.That is, the Slice
method is not called if there is also a Range
indexer.
This means that if we want to support Slice
in F#, we at the very least also need to be aware of indexers taking System.Range
.
If we went ahead with adding support for Slice
alone, and if a given type had both a System.Range
indexer and a Slice
method, then using slicing syntax on it in C# would call the System.Range
indexer, while using slicing syntax on it in F# would call the Slice
method.
Presumably this disparity would be undesirable, since this[Range]
and Slice
could have entirely different behaviors, return types, etc. Changing our minds about this later would be a breaking change.
This problem holds even though the BCL itself has only added Slice
methods and has not added any Range
indexers, because this would still affect any user-defined types that have both this[Range]
and Slice(int, int)
.
There are a few different ways forward here. Of the following, the first is straightforward (I have it working); the second is doable (I have it mostly working); the third is more involved.
Range
indexers, but don't support using them with slicing syntaxIn theory, we could still implement this suggestion (supporting Slice
) without making any further decisions about System.Range
or System.Index
by simply detecting whether an indexer taking System.Range
is present. If one is present, we would raise an error (just as happens now), whether or not a Slice
method is also present. If we later added full support for System.Range
, we would then simply start calling the Range
indexer for such types.
Range
indexers with slicing syntax without (yet) adding full support for System.Range
itselfAlternatively, we could add only enough support for System.Range
and System.Index
to make this feature work.
In the compiler, we could look for a System.Range
indexer, and, if found, look for its new : Index * Index -> Range
constructor. We could then look for the necessary members on Index
to help construct the appropriate Range
value from the F# start
and/or finish
int
values. The compiler would simply emit calls to the constructors and methods it found with the appropriate paths, names, and signatures, and pass the constructed Range
into the indexer.
We could do this without actually supporting System.Range
or System.Index
anywhere else.
System.Range
and System.Index
Third, we could write an RFC and implementation for #1044, and probably also revisit RFC FS-1076.
This would probably be good to do eventually, although I don't know if I'm willing to do all of this myself right now, unless someone is willing to sponsor me :)
So I have done some more experimenting, and it is feasible to add support for Slice
, System.Index
and System.Range
indexers, and from-end indexing/slicing (i.e., a new approach to RFC FS-1076) all at once — with the following questions and caveats:[^1]
Slice
Slice
only supports a Slice
method taking parameters of type System.Int32
. F#'s support for GetSlice
allows the parameters to be of any type.
Slice
only supports a Slice
method taking exactly two parameters. F#'s support for GetSlice
allows any number of parameters, enabling support for $n$-dimensional slicing.
Slice
is to add slicing support for 1-dimensional BCL types, not to add another way to do $n$-dimensional slicing. If someone wants $n$-dimensional slicing, they can use GetSlice
or an indexed property with multiple System.Range
parameters.)Slice
only supports intrinsic members. All of the existing F# indexing and slicing support allows extension members as well.
System.Index
and System.Range
int
index and range expressions into calls to Index
and Range
indexers by detecting the well-known names and shapes and generating calls to the appropriate constructors and members. We don't need a higher target framework to do this.
int
values with units of measure as well, e.g., for a type having member Item : 'T with get (i : System.Index)
, should expr[^3<m>]
compile?The C# spec considers a type "countable" in 1 dimension if it has an appropriately-typed getter named Length
or Count
. This enables implicit support for 1-dimensional from-end indexing and slicing for types with non-System.Index
/System.Range
indexed properties (i.e., indexers taking int
).
We could do the same in F#, with a few adjustments for things like end-inclusiveness, bounds tolerance for slicing, etc.
The C# design does not, however, include implicit support for $n$-dimensional from-end indexing or slicing with non-System.Index
/System.Range
indexers. I suspect the rationale in C# is that new $n$-dimensional types should simply expose System.Index
/System.Range
indexers; supporting existing $n$-dimensional slicing wasn't a consideration, since there was none. (Update: one of the C# team members has said that they didn't decide against implicit support for $n$-dimensional from-end indexing/slicing; they just didn't consider it at all.)
We would probably want to support $n$-dimensional from-end indexing/slicing in F# (as RFC FS-1076 tried to do), including for existing types where possible. One way to do so (an alternative to GetReverseIndex
) would be to establish a new pattern whereby a type is considered "countable" in $n$ dimensions when it has an appropriately-typed intrinsic or extension method with the signature member GetLength : dimension:int -> 'Length
. This would work for $n$-dimensional arrays, since System.Array
already has such a method. I think it would be a reasonable pattern for any user-defined type seeking to support $n$-dimensional from-end indexing/slicing to follow as well.
GetLength
pattern acceptable? Will C# later come back and add implicit support that will follow a different pattern?System.Index
/System.Range
$n$-dimensional from-end indexing/slicing. Edit: I was not aware of https://github.com/dotnet/runtime/issues/100924, although I don't see any mention there yet of language support for implicit conversion of NIndex
[like ^1
] to nint
via a generated call to GetOffset
, etc., and it doesn't sound like it's likely.)[^1]: Yes, I'm eliding many details. I hope to be able to get an RFC organized soon.
Could we support int voption
as well? (maybe even int64 voption
)
@nodakai
Could we support
int voption
as well? (maybe evenint64 voption
)
Are you asking that we update the existing support for GetSlice
to recognize voption
? I think that should be a separate suggestion.
The support for Slice
wouldn't involve options (value or reference) at all.
@nodakai
Could we support
int voption
as well? (maybe evenint64 voption
)Are you asking that we update the existing support for
GetSlice
to recognizevoption
? I think that should be a separate suggestion.The support for
Slice
wouldn't involve options (value or reference) at all.
Yeah, should be a new suggestion, but I don't think we want to have any implicit conversions from/to options.
I propose we convert slice syntax not only to
GetSlice
call but toSlice
call that is now implemented by all collectionsThe existing way of approaching this problem in F# is implementing SRTP extension method
Pros and Cons
The advantages of making this adjustment to F# are all slicing available in C# will work in F#
The disadvantages of making this adjustment to F# are not exist
Extra information
Estimated cost (XS, S, M, L, XL, XXL): XS
Related suggestions:
Affidavit (please submit!)
Please tick these items by placing a cross in the box:
Please tick all that apply:
For Readers
If you would like to see this issue implemented, please click the :+1: emoji on this issue. These counts are used to generally order the suggestions by engagement.