Proposal: lambda array initializers #976
Replies: 11 comments
-
Or, instead of new syntax, we could just make the existing array initialization pattern i.e. a |
Beta Was this translation helpful? Give feedback.
-
@alrz that's true, but that's a lot of work for the compiler: it has to realize that this loop (which can be anywhere) initializes every item of the array. It would be much easier to extend it to allow us to say "now it's non-nullable, dammit!" after the loop. |
Beta Was this translation helpful? Give feedback.
-
A new syntax is a lot more costly than an additional pass to check if the array is initialized. Also, it does not have to (and can't really) cover all cases, just straightforward loops that initialize the array (the exact same thing that your syntax will be translated to) works fine. |
Beta Was this translation helpful? Give feedback.
-
I am not sure about lambdas even if they provide extreme flexibility. Doesn't it close the door to some compile-time optimization techniques? My preference would be some sort of syntax consistent with existing and also slicing #185. var array = new [10; ""]; //string inferred
var array = new string[10; i => i.ToString()]; Also couldn't help it 😛 : var lambdas = new Func<int>[1000] i => j => j*i;
public int[] Prop => new int[1000] i => i*i; |
Beta Was this translation helpful? Give feedback.
-
Except that it won't work with existing code. The thing is that you are addressing an analysis problem with a new syntax while it's compiler's job to recognize the pattern and produce appropriate warnings. |
Beta Was this translation helpful? Give feedback.
-
@alrz this is not to solve the null analysis, this is just fixing the syntax gap. |
Beta Was this translation helpful? Give feedback.
-
You have to update code like |
Beta Was this translation helpful? Give feedback.
-
What's wrong with just adding a library method for array initialization? E.g. instead of: string[] squares = new string[1000] i => (i*i).ToString(); you would write: string[] squares = Array.Create<string>(1000, i => (i*i).ToString()); Or would the performance hit of calling the delegate for each item be a problem? |
Beta Was this translation helpful? Give feedback.
-
I'm a bit concerned about the readability of the proposed syntax - having a lambda directly following a declaration feels off to me. (Does anyone else share this concern? If it's just me then ignore me 😆) That said, I don't know if I have a very good suggestion to replace it. The following comes to mind, requiring the lambda to fall inside curly braces: string[] x = new string[1000] { i => i.ToString() }; But this clashes with the syntax for initializing an array already, not to mention the following case looks worse imho: string[] x = new string[1000] { i => {
return i.ToString();
}}; Some food for thought... |
Beta Was this translation helpful? Give feedback.
-
Enumerable.Range(1000).Select(i => (i*i).ToString()).ToArray() not effective, but at least available |
Beta Was this translation helpful? Give feedback.
-
Kotlin also has array initializers to ensure non-nullability of array elements. I was curious how would they look in C#, and I must say I am quite impressed. They provide a compact way to declare and initialize arrays, simplify existing code and tests often to a single line, and move closer to functional programming. Definitely want them as a C# feature. Signatures of the methods I have used to emulate array initializers: public static class Arrays {
public static T[] New<T> (int length, T defaultValue);
public static T[] New<T> (int length, Func<int, T> f);
public static T[] New<T> (int length, Func<int, T[], T> f);
public static T[,] New<T> (int length1, int length2, T defaultValue);
public static T[,] New<T> (int length1, int length2, Func<int, int, T> f);
public static T[,] New<T> (int length1, int length2, Func<int, int, T[,], T> f);
} Example usages from an actual project: Create an empty array with new T() values instead of nulls: T[] v = Arrays.New<T>(length, new T()); Convert an analog filter to a digital filter: Coefficients = Arrays.New(Right - Left + 1, i => analog[(i + Left) / scale]); Convert an analog filter to a 2D radial filter: Coefficients = Arrays.New(Right - Left + 1, Bottom - Top + 1,
(x, y) => filter[Math.Sqrt(Sqr(x + Left) + Sqr(y + Top)) / scale]); Convert a digital filter to a 2D separable filter: Coefficients = Arrays.New(Right - Left + 1, Bottom - Top + 1,
(x, y) => filter[x + Left] * filter[y + Top]); Initialize an integral array where we also need the reference to the newly created array: integral = Arrays.New<T>(array.Length + 1,
(i, v) => i == 0 ? new T() : (dynamic) v[i - 1] + array[i - 1]); Convolute an array with a filter at every index: public T[] Convolute (T[] source) => Arrays.New(source.Length, i => Sample(source, i)); Downsample an array: public T[] Downsample (T[] source, int factor, int shift) =>
Arrays.New(
(source.Length - shift + factor - 1) / factor,
i => Sample(source, i * factor + shift)); Convert a lowpass filter to a highpass filter: return Arrays.New(filter.Right - filter.Left + 1,
i => i == -filter.Left ? 1 - filter[0] / sum : -filter[i + filter.Left] / sum); Add two arrays: return New<T>(u.Length, i => (dynamic) u[i] + v[i]); Subtract two arrays: return New<T>(u.Length, i => (dynamic) u[i] - v[i]); Create an empty image with new T() values instead of nulls: public Image (int xs, int ys) : this(Arrays.New<T>(ys, xs, new T())) {} Create a new image: public Image (int xs, int ys, Func<int, int, T> f) :
this(Arrays.New(ys, xs, (y, x) => f(x, y))) {} Create a new image from a Bitmap: return new Image<T>(bitmap.Width, bitmap.Height,
(x, y) => Colors.To<T>(bitmap.GetPixel(x, y))); Create a random image: private Image<T> CreateRandomImage<T> (int xs, int ys) where T : new() => new Image<T>(xs, ys,
(x, y) => Colors.To<T>(Color.FromArgb(random.Next(256), random.Next(256), random.Next(256)))); Convolute an image with a filter at every pixel: public Image<T> Convolute (Image<T> source) => new Image<T>(source.Xs, source.Ys,
(x, y) => Sample(source, x, y)); Downsample an image: public Image<T> Downsample (Image<T> source, int factor = 2, int shift = 0) =>
new Image<T>(
(source.Xs - shift + factor - 1) / factor,
(source.Ys - shift + factor - 1) / factor,
(x, y) => Sample(source, x * factor + shift, y * factor + shift)); Add two images: return new Image<T>(image1.Xs, image1.Ys, (x, y) => (dynamic) image1[x, y] + image2[x, y]); Subtract two images: return new Image<T>(image1.Xs, image1.Ys, (x, y) => (dynamic) image1[x, y] - image2[x, y]); Create a Rows indexer of an image: get => Arrays.New(image.Ys, y => image[x, y]); Create a Columns indexer of an image: get => Arrays.New(image.Xs, x => image[x, y]); Finally, here are all of the tests for my FFT implementation, many of them simplified to only two lines of code: [Test]
public void Simple_example () {
Complex[] time = Arrays.New<Complex>(8, t => t == 1 ? 1 : 0);
Complex[] freq = {
new Complex(0.125, 0), new Complex(0.088, -0.088), new Complex(0, -0.125), new Complex(-0.088, -0.088), new Complex(-0.125, 0),
new Complex(-0.088, 0.088), new Complex(0, 0.125), new Complex(0.088, 0.088)
};
Assert.That(fft.Forward(time), Is.EqualTo(freq).Using(Complex.Within(1E-6)));
}
[Test]
public void Random_signal () {
for( int n = 1; n <= maxn; n *= 2 ) {
Complex[] time = Arrays.New(n, t => new Complex(random.NextDouble(), random.NextDouble()));
Complex[] freq = fft.Forward(time);
AssertTimeFreq(time, freq);
}
}
[Test]
public void Constant_signal () {
for( int n = 1; n <= maxn; n *= 2 ) {
Complex[] time = Arrays.New<Complex>(n, t => 1);
Complex[] freq = Arrays.New<Complex>(n, f => f == 0 ? 1 : 0);
AssertTimeFreq(time, freq);
}
}
[Test]
public void Cis_signal () {
for( int n = 16; n <= maxn; n *= 2 ) {
Complex[] time = Arrays.New(n, t => Complex.Cis(2 * Math.PI * 10 * t / n));
Complex[] freq = Arrays.New<Complex>(n, f => f == 10 ? 1 : 0);
AssertTimeFreq(time, freq);
}
}
[Test]
public void Cos_signal () {
for( int n = 16; n <= maxn; n *= 2 ) {
Complex[] time = Arrays.New<Complex>(n, t => Math.Cos(2 * Math.PI * 10 * t / n));
Complex[] freq = Arrays.New<Complex>(n, f => f == 10 ? 0.5 : f == n - 10 ? 0.5 : 0);
AssertTimeFreq(time, freq);
}
}
[Test]
public void Sin_signal () {
for( int n = 16; n <= maxn; n *= 2 ) {
Complex[] time = Arrays.New<Complex>(n, t => Math.Sin(2 * Math.PI * 10 * t / n));
Complex[] freq = Arrays.New(n, f => f == 10 ? new Complex(0, -0.5) : f == n - 10 ? new Complex(0, 0.5) : 0);
AssertTimeFreq(time, freq);
}
}
private void AssertTimeFreq (Complex[] time, Complex[] freq) {
Assert.That(fft.Forward(time), Is.EqualTo(freq).Using(Complex.Within(1E-16)));
Assert.That(fft.Inverse(freq), Is.EqualTo(time).Using(Complex.Within(1E-16)));
} For the syntax itself I see several options: T[] v = new T[length]: new T();
T[] v = new T[length], new T();
T[] v = new T[length] = new T();
T[] v = new T[length](new T());
T[] v = new T[length]{new T()};
Complex[] time = new T[n]: t => Complex.Cis(2 * Math.PI * 10 * t / n);
Complex[] time = new T[n], t => Complex.Cis(2 * Math.PI * 10 * t / n);
Complex[] time = new T[n] = t => Complex.Cis(2 * Math.PI * 10 * t / n);
Complex[] time = new T[n](t => Complex.Cis(2 * Math.PI * 10 * t / n));
Complex[] time = new T[n]{t => Complex.Cis(2 * Math.PI * 10 * t / n)}; I prefer the first options, the others are either ambiguous or verbose. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Problem
You cannot create an array of reference types that doesn't contain nulls unless you explicitly enumerate them all. The compromises the soundness of the nullability checker.
Proposed solution
For explicitly sized arrays a lambda initializer should be permitted, accepting a single index parameter.
If the initializer never returns
null
or a nullable value, the whole array is inferred to be non-nullable, except in catch/finally blocks.The lambda of course can be lowered into a simple loop after the checker is satisfied.
Open questions
Should other kinds of delegates be allowed in this position? They will look rather weird (
new string[n] Utils.GetElement
), perhaps an extra token is needed for legibility?Beta Was this translation helpful? Give feedback.
All reactions