The foreach<T> generic constraint #1945
Replies: 7 comments
-
Shapes (#164) should solve this problem in a much more general manner. |
Beta Was this translation helpful? Give feedback.
-
@yaakov-h
|
Beta Was this translation helpful? Give feedback.
-
Problem 3 could be solved with a simple wrapper type, and would be the most correct definition considering you're trying to unify two similar structured, but go for a lowest common denominator. If you just try solve it for using System;
using System.Collections.Generic;
public static class Program
{
public static void Main(string[] args)
{
foreach (var arg in args.AsForEach())
{
Console.WriteLine(arg);
}
Console.WriteLine();
foreach (var arg in args.AsSpan().AsForEach())
{
Console.WriteLine(arg);
}
}
}
static class ForEachExtensions
{
public static ForEach<T> AsForEach<T>(this IEnumerable<T> e)
{
return new ForEach<T>(e);
}
public static ForEach<T> AsForEach<T>(this Span<T> s)
{
return new ForEach<T>(s);
}
}
ref struct ForEach<T>
{
public ForEach(IEnumerable<T> e)
{
enumerable = e;
span = default;
}
public ForEach(ReadOnlySpan<T> s)
{
enumerable = default;
span = s;
}
readonly IEnumerable<T> enumerable;
readonly ReadOnlySpan<T> span;
public Enumerator GetEnumerator() => new Enumerator(enumerable, span);
public ref struct Enumerator
{
public Enumerator(IEnumerable<T> enumerable, ReadOnlySpan<T> s)
{
enumerator = enumerable?.GetEnumerator();
span = s;
spanIndex = -1;
}
readonly IEnumerator<T> enumerator;
readonly ReadOnlySpan<T> span;
int spanIndex;
public bool MoveNext()
{
if (enumerator != null) return enumerator.MoveNext();
return ++spanIndex < span.Length;
}
public T Current
{
get
{
if (enumerator != null) return enumerator.Current;
return span[spanIndex];
}
}
}
} |
Beta Was this translation helpful? Give feedback.
-
Going off @yaakov-h but making it even shorter (almost half as short) for simplicity: using System;
using System.Collections.Generic;
public static class Program
{
public static void Main(string[] args)
{
foreach (var arg in args.AsForEach())
Console.WriteLine(arg);
Console.WriteLine();
foreach (var arg in args.AsSpan().AsForEach())
Console.WriteLine(arg);
}
}
public static class ForEachExtensions
{
public static ForEach<T> AsForEach<T>(this IEnumerable<T> e) => new ForEach<T>(e);
public static ForEach<T> AsForEach<T>(this Span<T> s) => new ForEach<T>(s);
}
public ref struct ForEach<T>
{
private readonly IEnumerable<T> enumerable;
private readonly ReadOnlySpan<T> span;
public ForEach(IEnumerable<T> e) => (enumerable, span) = (e, default);
public ForEach(ReadOnlySpan<T> s) => (enumerable, span) = (default, s);
public Enumerator GetEnumerator() => new Enumerator(enumerable, span);
public ref struct Enumerator
{
private readonly IEnumerator<T> enumerator;
private readonly ReadOnlySpan<T> span;
private int spanIndex;
public Enumerator(IEnumerable<T> enumerable, ReadOnlySpan<T> s) => (enumerator, span, spanIndex) = (enumerable?.GetEnumerator(), s, -1);
public bool MoveNext()
{
if (enumerator != null) return enumerator.MoveNext();
return ++spanIndex < span.Length;
}
public T Current { get {
if (enumerator != null) return enumerator.Current;
return span[spanIndex];
}}
}
} |
Beta Was this translation helpful? Give feedback.
-
Why even have singel wrapper struct for all these cases? Why not just have an SEnumerable and SEnumerator shapes, and then just have hte appropriate witnesses for them perf type being wrapped? |
Beta Was this translation helpful? Give feedback.
-
Idea: Your proposal could be generalized to be duck typing-like constraint, e.g. : int GetEffectiveLength(T enumerable) where T: GetEnumerator() // IEnumerable, ReadOnlySpan
async T DoSomethingMore(T task) where T: GetAwaiter() // Task or ValueTask |
Beta Was this translation helpful? Give feedback.
-
@spixy à la F# |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
So, why would we need this?
Suppose a function obtaining an approximate typographic width in a library:
Now some of the input sources are of fixed length.
Let's try changing the input type to
ReadOnlySpan<char>
...Seems like either
ReadOnlySpan<char>
andIEnumerable<char>
cannot fit both worlds.As libraries should accommodate as many use cases as possible, we would result in something like this:
Now that is duplication of code.
How would
foreach<T>
look like?What could be a
foreach<T>
?Anything that could be used in a
foreach
statement, aka anything matching the definition ofIEnumerable<T>
structurally. For example:Span safety?
YES. As Spans can be used in such a statement, the same rules for Spans will be enforced for foreach<T>.
The only meaningful operation allowed for
foreach<T>
is to literally enumerate it in aforeach
statement.To use
foreach<T>
as a field, put it in aref struct
.What methods on
foreach<T>
are available?ToString() only. As with Spans, GetHashCode() and Equals() make no sense here and should be hidden.
We should also not expose GetEnumerator() here as we cannot determine the type of it statically inside the method (it is inferred at the call site), and writing custom enumerator functions are very rare in practice.
Shapes and extensions? Intersection types?
Reference:
TEnumerable : foreach<TItem>
Design 1:
TEnumerable : SEnumerable<TItem>, maybe ref struct
Problem 1: You can't tell the
SEnumerator<TItem>
in theSEnumerable<TItem>
to be a ref struct conditionally.Design 2:
TEnumerable : Span<TItem> | ReadOnlySpan<TItem> | IEnumerable<TItem>
Problem 2: This excludes ref structs that match
IEnumerable<TItem>
structurally.Design 3:
TEnumerable : SRefEnumerable<TItem> | SEnumerable<TItem>
Problem 3: We have to define two new shapes for such a basic use. It's a bit redundant. We could make
foreach<TItem>
a shorthand for this though.Type classes?
See the disadvantages in Section 2.3.4 of this document
Beta Was this translation helpful? Give feedback.
All reactions