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

Запретить ограничение where T: System.Array и where T: Object. В C# именно так и сделано #1986

Closed
SunSerega opened this issue Jun 7, 2019 · 31 comments

Comments

@SunSerega
Copy link
Contributor

function f1<TArray>: TArray;
where TArray: System.Array;
begin
  
  Result := TArray(System.Array(
    new integer[10]
  ));
  
  Result.Length.Println; // выводит 107, всегда, не зависимо от того - какой массив брать выше
  
end;

begin
  
  f1&<array of integer>;
  
end.

С пользовательскими типами воспроизвести не удалось.
Но я покопался в IL - похоже там добавляет лишний оператор box к типу TArray

@miks1965
Copy link
Contributor

miks1965 commented Jun 7, 2019

О, прелесть какая в C#:

Ограничение не может быть специальным классом "Array"

Исправьте пожалуйста заголовок Issue

@SunSerega
Copy link
Contributor Author

SunSerega commented Jun 7, 2019

Нет, подождите, при чём тут. Ограничение в виде System.Array работает:

procedure p1<TArray>;
where TArray: System.Array;
begin
  
  var a := TArray(System.Array(
    new integer[10]
  ));
  
  a.Length.Println; // тут всё норм
  
end;

begin
  
  p1&<array of integer>;
  
end.

Я же говорю, проблема в том что компилятор добавляет лишний оператор box, если дело касается переменной Result. А так - там всё в порядке.

Более того, эта фича очень нужна для OpenCLABC:

    public function GetArrayAt<TArray>(offset: CommandQueue<integer>; szs: CommandQueue<array of integer>): TArray; where TArray: &Array;
    public function GetArray<TArray>(szs: CommandQueue<array of integer>): TArray; where TArray: &Array; begin Result := GetArrayAt&<TArray>(0, szs); end;

Эти методы создают новый массив типа TArray с размерностями szs, заполняя их содержимым из объекта OpenCL.cl_mem.

И это вполне работает, с небольшим костылём. Вот в примере SimpleAddition, в самом конце:

  A.GetArray&<array of integer>(10).Println;

Это работает и выводит правильный результат. А костыль заключается только в том, что .Length массива считается через другие переменные:

function KernelArg.GetArrayAt<TArray>(offset: CommandQueue<integer>; szs: CommandQueue<array of integer>): TArray;
begin
  var el_t := typeof(TArray).GetElementType;
  
  var szs_val: array of integer := Context.Default.SyncInvoke(szs);
  Result := TArray(System.Array.CreateInstance(
    el_t,
    szs_val
  ));
  
  //ToDo #1986
//  var res_len := Result.Length;
  var res_len := szs_val.Aggregate((i1,i2)->i1*i2);
  
  Context.Default.SyncInvoke(
    self.NewQueue
    .ReadData(Result, offset, Marshal.SizeOf(el_t) * res_len) as CommandQueue<KernelArg> //ToDo #1981
  );
  
end;

Вот это szs_val.Aggregate((i1,i2)->i1*i2); заменяет Result.Length, хотя так, конечно, медленнее.

@EmilyGraceSeville7cf
Copy link

О, прелесть какая в C#:

Меня радует, что Вы обратились к C#. Но здесь PascalABC.NET, а не C#, давайте не будем уходить в обсуждение C#. =)

@ibond84
Copy link
Contributor

ibond84 commented Jun 8, 2019

function f1<T>: array of T;
begin
  Result := new T[10];
  Result.Length.Println;
end;

begin
  f1&<integer>;
end.

Зачем вы используете where, если можно написать так. Я бы тоже запретил спецификатор where T:Array

@SunSerega
Copy link
Contributor Author

SunSerega commented Jun 8, 2019

OpenCL буфер может хранить массив любой размерности, одномерные это только пример.

Я бы тоже запретил спецификатор where T:Array

В чём прикол запрещать всё подряд? Баг только с Result!

@SunSerega
Copy link
Contributor Author

SunSerega commented Jun 8, 2019

А, и, это всё только внутри функции:

function f1<TArray>: TArray;
where TArray: System.Array;
begin
  
  Result := TArray(System.Array(
    new integer[10]
  ));
  
end;

begin
  
  f1&<array of integer>.Length.Println; // 10, всё нормально
  
end.

После того как Result возвратили - всё нормально.

И если передать куда то внутри - тоже норм:

procedure p1(a: System.Array);
begin
  a.Length.Println; // опять же, 10
end;

function f1<TArray>: TArray;
where TArray: System.Array;
begin
  
  Result := TArray(System.Array(
    new integer[10]
  ));
  
  p1(Result);
end;

begin
  
  f1&<array of integer>.Println;
  
end.

@ibond84
Copy link
Contributor

ibond84 commented Jun 8, 2019

А зачем описывать генерик-функцию с ограничителем и писать кучу преобразований типов, если можно написать обычную?

@SunSerega
Copy link
Contributor Author

SunSerega commented Jun 8, 2019

Я же говорю, там читает массив любой размерности, не только одномерный. Вот, ещё раз, тот кусок кода из OpenCLABC:

function KernelArg.GetArrayAt<TArray>(offset: CommandQueue<integer>; szs: CommandQueue<array of integer>): TArray;
begin
  var el_t := typeof(TArray).GetElementType;
  
  var szs_val: array of integer := Context.Default.SyncInvoke(szs);
  Result := TArray(System.Array.CreateInstance(
    el_t,
    szs_val
  ));
  
  //ToDo #1986
//  var res_len := Result.Length;
  var res_len := szs_val.Aggregate((i1,i2)->i1*i2);
  
  Context.Default.SyncInvoke(
    self.NewQueue
    .ReadData(Result, offset, Marshal.SizeOf(el_t) * res_len) as CommandQueue<KernelArg> //ToDo #1981
  );
  
end;

Это сейчас работает, так что не надо его ломать.

@ibond84
Copy link
Contributor

ibond84 commented Jun 8, 2019

Я же говорю, там читает массив любой размерности, не только одномерный

 Result := TArray(System.Array.CreateInstance(
    el_t,
    szs_val
  ));

Ну, здесь только одномерный создается. А TArray может быть многомерным. Мне теперь понятно, почему в C# это запрещено. Потому что очень много рефлексии будет, что конечно безобразие. Для каждой размерности надо писать свою перегруженную функцию.

@SunSerega
Copy link
Contributor Author

SunSerega commented Jun 8, 2019

Ну, здесь только одномерный создается

С чего вдруг? szs_val этоarray of integer, и сколько у него значений - столько измерений будет у массива.

Потому что очень много рефлексии будет

Ничего подобного, она там не нужна, и как раз именно потому что можно сразу преобразовать к TArray - рефлекция вообще практически не нужна. А чтоб без него работало - придётся костыли делать. Кроме того, если не использовать тот маленький кусочек рефлекции (typeof(TArray).GetElementType) - понадобится по 50 перегрузок для каждой функции, что точно не хорошо. При чём не только этой функции, а сразу и 8 других.

Для каждой размерности надо писать свою перегруженную функцию.

А это ограничит макс кол-во измерений, что не хорошо.

@miks1965
Copy link
Contributor

miks1965 commented Jun 8, 2019

The following types may not be used as constraints:

    System.Object
    System.Array
    System.Delegate
    System.Enum
    System.ValueType.

@SunSerega
Copy link
Contributor Author

Ну и прекрасно, в C# вообще на много больше запрещено чем в паскале. Как статичные индексные свойства.

А в этом запрете однозначно нет смысла (ну, кроме System.Object). Всё остальное - только заставляет заниматься огромным кол-вом копипаста, делая много перегрузок каждой функции, что однозначно плохо.

И главное, от того что сделаешь 100 перегрузок 1 функции - её универсальность всё равно будет на том же уровне что у шаблонной.

@SunSerega
Copy link
Contributor Author

И главное, всё это началось с того - что есть 1 мелкий баг. Если его исправить - всё остальное уже работает, ничего дописывать не придётся. Поэтому и запрещать не к чему.

@miks1965
Copy link
Contributor

miks1965 commented Jun 8, 2019

Я уверен, что в таком большом языке как C# это сделано не случайно. Видимо, есть серьёзные проблемы с ограничениями подобного рода.

Все кроме Object здесь являются особыми типами, многообразные подтипы которых вмонтированы в синтаксис языка.

Так что я думаю, что здесь надо запретить все эти типы пока не начали сыпаться худшие проблемы

@miks1965
Copy link
Contributor

miks1965 commented Jun 8, 2019

Хотя в C# 7.3 внезапно разрешили все ограничения кроме System.Array:
https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/generics/constraints-on-type-parameters#why-use-constraints

@SunSerega
Copy link
Contributor Author

SunSerega commented Jun 8, 2019

Так что я думаю, что здесь надо запретить все эти типы пока не начали сыпаться худшие проблемы

Вот если будут - можно подумать о запрете. Но я уверен что их и не будет, потому что уже видно что оно всё работает!

Все кроме Object здесь являются особыми типами, многообразные подтипы которых вмонтированы в синтаксис языка.

Тем не менее. Все наследники System.Array (с элементами - размерного типа) - можно блокировать с помощью GCHandle. И этого уже достаточно. А так - у System.Array ещё есть специальные методы для заполнения/чтения элемента имея произвольное - кол-во индексов. Ну и конечно получение размерности массива тоже.

Так же с делегатами - у них там DynamicInvoke. А у энумов - статичные методы.

То что это всё потихоньку разрешают - только показывает что они начали одумываться о тупых запретах. И - как я и сказал, в C# нельзя делать статичные индексные свойства. Вы думаете от них тоже что сыпаться будет? В C# полно тупых запретов, имеющих только примерные и надуманные причины.

@EmilyGraceSeville7cf
Copy link

EmilyGraceSeville7cf commented Jun 9, 2019

В C# полно тупых запретов, имеющих только примерные и надуманные причины.

Умерьте свой пыл. Эмоции здесь ни к чему и ничего не изменят. Запретов хватает везде, но, давайте переведём диалог в более мирное русло.

Вот если будут - можно подумать о запрете.

То есть, Вы хотите, чтобы сначала делали, а потом думали? Может, стоит делать наоборот?

@Help01
Copy link

Help01 commented Jun 9, 2019

То есть, Вы хотите, чтобы сначала делали, а потом думали? Может, стоит делать наоборот?

Проверка боем! Этих ограничений в Net нет. А C# не единственный Net язык. А если делать наоборот, то по-моему вы подумали лишь о пути наименьшего сопротивления, но так и не нашли причины ограничений.

@EmilyGraceSeville7cf
Copy link

EmilyGraceSeville7cf commented Jun 9, 2019

Проверка боем! А если делать наоборот, то по-моему вы подумали лишь о пути наименьшего сопротивления, но так и не нашли причины ограничений.

Не согласен. Аналогия: сначала разрабатывается алгоритм решения задачи, потом она программируется. Наоборот не делается. Так и здесь.

@SunSerega
Copy link
Contributor Author

Это если разбираться. Считая что сказали разрабы выше - они на это время тратить не хотят. А если не разбираться - остаётся только тестировать.

@Help01
Copy link

Help01 commented Jun 9, 2019

Не согласен. Аналогия: сначала разрабатывается алгоритм решения задачи, потом она программируется. Наоборот не делается. Так и здесь.

Аналогия: того, кто думает, что все заработает сразу, ждет сильное разочарование.
Ни одна сложная система не пишется в один присест и не может быть обдумана заранее. Сейчас вопрос лишь в том, насколько вы готовы сражаться с багами. Ну а про ваше "подумать" я уже сказал. Вы подумали лишь о том, что лучше не пытаться, тогда ни проблем, ни рисков. Но опять же все упирается в то, хотите ли вы заниматься гипотетически возможной многочасовой отладкой и тестами.

@SunSerega
Copy link
Contributor Author

SunSerega commented Jun 9, 2019

Отладка вряд ли понадобится если что. Если что то будет сыпаться от такой конструкции - получим ошибку неправильного IL. Если в IL всё будет выглядеть правильно (что я сам за минуту могу проверить) - станет понятно что так делать просто нельзя.

Но - я после создания этой issue прошёлся ещё по нескольким ситуациям (и не только тем что написал выше), и всё работает нормально. Проблема только с Result и только в том что генерируется лишний IL оператор, без которого всё будет работать.

@Help01
Copy link

Help01 commented Jun 9, 2019

Отладка вряд ли понадобится если что. Если что то будет сыпаться от такой конструкции - получим ошибку неправильного IL.

Вряд ли, я думаю не отсюда стоит ждать неприятностей. Скорее всего в каком-то другом месте с какой-то фичей что-то случиться. Но что об этом спорить, надо делать-мучаться, тогда и увидим.

@miks1965 miks1965 changed the title Генерирует неправильную программу, в случае с where и массивами Запретить ограничение where T: System.Array и where T: Object Jun 10, 2019
@miks1965 miks1965 changed the title Запретить ограничение where T: System.Array и where T: Object Запретить ограничение where T: System.Array и where T: Object. В C# именно так и сделано Jun 10, 2019
@AIexandrKotov
Copy link
Contributor

мда

@Pavel314
Copy link

Pavel314 commented Jun 11, 2019

Конструкция where T: &Array должна быть запрещена, важный аргумент заключается в возможности обхода стандартной индексации с 0:

function f<T>(): T;where T: &Array;
begin
  Result := T(&Array.CreateInstance(typeof(integer), new integer[2](10, 10), new integer[2](-1, -1)));
end;

begin
  var a := f&<array[,] of integer>();
  Writeln(a[-1, -1]);
end.

Здесь двумерный массив используется умышленно, так как для одномерных массивов индексирующихся не c 0, отведён специальный тип - T[*].
С другой стороны проблема глубже, чем казалась:

type
  arr = array[,] of integer;

function f<T>(): T;where T: arr;
begin
  Result := T(&Array.CreateInstance(typeof(integer), new integer[2](10, 10), new integer[2](-1, -1)));
end;

begin
  var a := f&<array[,] of integer>();
  Writeln(a[-1, -1]);
end.

@SunSerega
Copy link
Contributor Author

SunSerega commented Jun 11, 2019

А в чём проблема? И Where T: Array не при чём вообще:

type
  TArr = array[,] of byte;

begin
  var a := TArr(System.Array.CreateInstance(typeof(byte), new integer[](10,10), new integer[](-1,-1)));
  a[-1,-1] := 5;
  writeln(a[-1,-1]);
end.

@AIexandrKotov
Copy link
Contributor

Ну так

А в чём проблема?

@SunSerega
Copy link
Contributor Author

Это уже, кстати, и в C# работает:

using System;

namespace CSrharpApplicationTest
{
    public class Program
    {
        public static void Main(string[] args)
        {
        	byte[,] a;
        	a = (byte[,])Array.CreateInstance(typeof(byte), new int[]{10, 10}, new int[]{-1, -1});
        	a[-1,-1] = 5;
        	Console.WriteLine(a[-1,-1]);
        	Console.ReadLine();
        }
    }
}

@EmilyGraceSeville7cf
Copy link

EmilyGraceSeville7cf commented Jun 11, 2019

PascalABC.NET уже имеет один способ для обхода индексации, но это не значит что должен быть и второй. Во-вторых, Вы свернули с изначальной темы - про where. Ищите, пожалуйста, аргументы в плоскости изначально поднятого вопроса.

@miks1965
Copy link
Contributor

miks1965 commented Jun 11, 2019

Нужен другой пример, показывающий, что ограничение where T: Array может приводить к некорректностям. Я пока такого не нашел.

Пример с отрицательными индексами - он как раз и говорит, что CreateInstance - мощная и позволяет создавать массивы с отрицательными индексами. Именно так мы моделируем индексы в статических массивах

@EmilyGraceSeville7cf
Copy link

EmilyGraceSeville7cf commented Jun 11, 2019

Именно так мы моделируем индексы в статических массивах

Подчеркну, что эта возможность (CreateInstance) - не для повседневного использования, ровно как и механизм рефлексии.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants