AA>
Так это он шейпы переименовал. И добавил моноиды в презентацию — чтобы хипстеры и менеджеры были хэппи. Так легче видимо эти шейпы будет пропихнуть.
Здравствуйте, hi_octane, Вы писали:
_>И добавил моноиды в презентацию — чтобы хипстеры и менеджеры были хэппи.
При чём тут хипстеры и тем более менеджеры; моноиды — обычная конструкция для инженеров.
У него, правда, неправильно названы члены шейпа: вместо Zero и + должны быть Identity и Combine(). Чтоб не ломать мозг для мультипликативного моноида, где Zero это 1, а + это *.
_>Да нужны.
Вообще да, но раньше и без них обходились, называя их policy.
_>>И добавил моноиды в презентацию — чтобы хипстеры и менеджеры были хэппи. Q>При чём тут хипстеры и тем более менеджеры;
Хипстеры тут при том что буквально каждый изучив эти самые монады и моноиды и решив что всё понял, бежит писать в блог свою версию объяснения, и оценочное суждение в духе "это офигенно". Учитывая что система типов C# мягко говоря не вывозит, такое информационное поле создаёт на создателей языка вполне ощутимое давление.
Менеджеры тут при том что они выделяют ресурсы на фичи. И легче всего им продать то что 1)хайповое 2)есть в другом языке которые "competitor".
Q>моноиды — обычная конструкция для инженеров.
Конструкция необычная. В моей практике (может просто так повезло с проектами) — людям сначала нравится элегантность и единообразность монад, моноидов и этого всего. Команда проникается крутостью подхода, всё как-бы очень красиво и работа кипит. Конструкции распространяются по коду. И через какое-то время процесс разработки встаёт колом. На любую задачу уровня "добавьте отдельный таймаут для операции N, и чтобы его можно было менять в конфиге" начинает уходить прорва времени неадекватная задаче и квалификации людей которые работают с монадным кодом. Анализ трэйсов и дампов становится мучением — там почти всегда просто сотни вложенных bind и apply, через которые могут продраться не только лишь все и мало кто хочет это делать. Люди просто забивают на любой способ отладки кроме гадания по логам. Когда дело доходить до оптимизации производительности или потребления памяти — часто проще всю монадную магию выкинуть и переписать с нуля на чистой императивщине.
При этом роли (шейпы) будут вполне полезны без всяких монад. Так что к ним претензий никаких.
Здравствуйте, hi_octane, Вы писали:
_>Хипстеры тут при том что буквально каждый изучив эти самые монады и моноиды и решив что всё понял _>В моей практике (может просто так повезло с проектами) — людям сначала нравится элегантность и единообразность монад _>начинает уходить прорва времени неадекватная задаче и квалификации людей которые работают с монадным кодом. _>Анализ трэйсов и дампов становится мучением — там почти всегда просто сотни вложенных bind и apply _>часто проще всю монадную магию выкинуть и переписать с нуля на чистой императивщине. _>При этом роли (шейпы) будут вполне полезны без всяких монад.
🤦♂️ Myötähäpeä
При чём тут монады я вообще не понял. Похожее по созвучию, что ли? Такую простыню накатать на абсолютно постороннюю тему; реально стыдно за людей, которые плюсуют такое. Если от самой простой алгебраической структуры «моноид» так подгорает, то что случится при встрече с ужасным словом «группа»?
Здравствуйте, hi_octane, Вы писали:
AA>> _>Так это он шейпы переименовал. И добавил моноиды в презентацию — чтобы хипстеры и менеджеры были хэппи. Так легче видимо эти шейпы будет пропихнуть.
_>Да нужны. Нам их ещё в C#9 обещали.
Я так понял главное там в том что в интерфейсе можно объявить static op_ и
и role для специализации
моноид это просто название интерфейса
и солнце б утром не вставало, когда бы не было меня
Здравствуйте, hi_octane, Вы писали:
_>каждый изучив эти самые монады и моноиды
Анекдот про тебя:
— Не люблю евреев!
— Почему?
— Они потопили «Титаник».
— Так ведь «Титаник» потопил айсберг?
— Да какая разница: АЙСБЕРГ, ВАЙСБЕРГ, ГРИНБЕРГ. Не люблю евреев!
Q>При чём тут монады я вообще не понял.
Ну так ты до этого не понял при чём там хипстеры и менеджеры
Q>Похожее по созвучию, что ли? Такую простыню накатать на абсолютно постороннюю тему;
Темы связаны потому что эти штуки друг без друга не ходят. Как только проект вырастет из хелло ворлд — понадобится из одних моноидов вызывать другие с сохранением какого-то контекста. И здрасьте вам, понадобились монады.
Q>Если от самой простой алгебраической структуры «моноид» так подгорает, то что случится при встрече с ужасным словом «группа»?
Как говорят в Одессе, мы таки покупаем или продаём? Если собеседование в модный стартапчик, то я готов с горящими глазами обсуждать группы, полугруппы, кольца, моноиды, функторы и всю эту дребедень. Но если мы таки обсуждаем практику — то у меня за последние много лет ощущение что чем серьёзнее в проекте ставка на "простые алгебраические структуры", тем больше у ПМ-а/инвестора вероятность через год обнаружить что "купил слона".
Здравствуйте, hi_octane, Вы писали:
_>Ну так ты до этого не понял при чём там хипстеры и менеджеры
Так ведь это ж ты не понял, чем отличается монада от хламидомонады : ) С менеджерами невпопад, так как для них наоборот работает: если моноид — что-то сложное из матана, то всё, пора из инженеров в менеджеры.
_>Темы связаны потому что эти штуки друг без друга не ходят.
_>Когда дело доходить до оптимизации производительности или потребления памяти — часто проще всю монадную магию выкинуть и переписать с нуля на чистой императивщине.
Внезапно, код с моноидом в стиле как на видео будет быстрее, чем стандартная лапша с делегатами : )
Рассмотрим моноид из видео (C# пятилетней давности подойдёт, ничего нового из превью 9.0). Осторожно, возможна интоксикация жутким хтоническим матаном в мозг!
public interface IMonoid<T>
{
T Identity { get; }
T Combine(T left, T right);
}
internal readonly struct AdditiveInt32Monoid : IMonoid<int>
{
public int Identity => 0;
public int Combine(int left, int right) => left + right;
}
internal readonly struct MultiplicativeDecimalMonoid : IMonoid<decimal>
{
public decimal Identity => 1m;
public decimal Combine(decimal left, decimal right) => left * right;
}
И напишем свёртку, аналог Linq Aggregate:
internal static T Reduce<T, TMonoid>(this IEnumerable<T> items, TMonoid monoid)
where TMonoid : IMonoid<T>
{
if (items is null)
throw new ArgumentNullException(nameof(items));
T result = monoid.Identity;
foreach (T item in items)
result = monoid.Combine(result, item);
return result;
}
Вызовы выглядят так:
[Benchmark(Baseline = true)]
public int ReduceIntegers() => s_integers.Reduce(default(AdditiveInt32Monoid));
[Benchmark]
public int AggregateIntegers() => s_integers.Aggregate(0, (left, right) => left + right);
[Benchmark]
public decimal ReduceDecimals() => s_decimals.Reduce(default(MultiplicativeDecimalMonoid));
[Benchmark]
public decimal AggregateDecimals() => s_decimals.Aggregate(1m, (left, right) => left * right);
Бенчмарк можно взять отсюда и убедиться самостоятельно.
Здравствуйте, hi_octane, Вы писали:
_>Конструкция Анализ трэйсов и дампов становится мучением — там почти всегда просто сотни вложенных bind и apply, через которые могут продраться не только лишь все и мало кто хочет это делать. Люди просто забивают на любой способ отладки кроме гадания по логам. Когда дело доходить до оптимизации производительности или потребления памяти — часто проще всю монадную магию выкинуть и переписать с нуля на чистой императивщине.
+1. Есть один знакомый архитектурный астронавт, у которого все через монады, бинды, апплу, эндофункторы, инверсии зависимостей и прочие подобные субстанции. Умножить на многословность и убогость системы типов сишарпа — и вуаля, блюдо готово. Реально код читать просто невозможно. Понять, что он делает — тем более. Блин, там, где нужно просто тупо вызвать form.ShowDialog, наверчено столько дерьма, что уму нерастяжимо. Видно, парень старается над job security. Если он свалит, весь этот кусок дерьма придется просто выкинуть.
Здравствуйте, AlexRK, Вы писали:
ARK>+1. Есть один знакомый архитектурный астронавт, у которого все через монады, бинды, апплу, эндофункторы, инверсии зависимостей и прочие подобные субстанции.
Впрочем, это легко решается. Достаточно переименовать IMonoid<T> в IGenericAggregationControllerManager<T>, как привычно простым менеджерАм, которые против этого вашего матана и рокет сайенс. Гляди-ка, совсем другое дело, специально для вас с hi_octane:
public interface IGenericAggregationControllerManager<T>
{
T Identity { get; }
T Combine(T left, T right);
}
Здравствуйте, varenikAA, Вы писали:
AA>
Пока не очень понятно, нужно ли.
Соображения:
0. 90% из того, о чём он рассуждает — это сложный способ разрешить определять operator+ за пределами типа.
В плюсах этой проблемы нет как класса благодаря наличию свободных функций; в шарпе этого нет — в итоге имеем печальный итог: мы не можем описать оператор+ для аргументов типа A и B, не будучи авторами A или B.
1. Рассказ про то, что у инта сразу два моноида, был убедителен, но непонятно, как именно это можно применить. Ну, кроме вырожденного случая "давайте проагрегируем массив интов с использованием 1 и * в качестве нашего моноида".
Ну, вот например — давайте определим умножение матриц с элементами типа T. По идее, нам надо как-то "скормить" в наш метод MatrixMultiply(T[,] a, T[,] b) оба моноида.
Ну, ок, мы, допустим, вызываем его так:
MatrixMultiply<IntAddMonoid, IntMulMonoid>(int[,] a, int[,] b).
А что мы пишем в тельце функции? Непонятно. В примере Мэдса функция AddAll обходилась одним моноидом. Мы могли привести аргумент к IntAddMonoid и получить сложение, могли — к IntMulMonoid, и получить умножение.
interface IMonoid<T, R>
{
static R Zero{get;}
static R operator+(T t1, T t2)
}
R[,] MatrixMultiply<M, A, R>(M[,] a, M[,] b)
where M:IMonoid<M>
where A:IMonoid<A>
{
int rA = a.GetLength(0);
int cA = a.GetLength(1);
int rB = b.GetLength(0);
int cB = b.GetLength(1);
if (cA != rB)
throw new InvalidOperationException("Argument size mismatch");
double[,] result = new double[rA, cB];
for (int i = 0; i < rA; i++)
for (int j = 0; j < cB; j++)
{
A t = A.Zero;
for (int k = 0; k < cA; k++)
{
A ab = a[i, k] + b[k, j]; // a & b are M, so the first monoid triggers
t += ab; // t & ab are A, so the second monoid triggers
}
result [i, j] = temp;
}
return result;
}
О май фрикин гад! Я специально расписал операции с временной переменной, т.к. иначе от знаков сложения начинает рябить в глазах.
Ну, и при вызове я не вижу способа обойтись без передачи третьего типа.
Так что целочисленные матрицы будут у нас умножаться через
int[,] a;
int[,] b;
var c = MatrixMultiply<IntMulMonoid, IntAddMonoid, int>(a, b)
В общем, кошмар на марше.
Не, я понял, что можно же напилить IntRing аналогичным образом, и иметь его и вдоль и поперёк. Но получается мы при выпиливании IntRing никак не можем повторно использовать заранее напиленные моноиды для сложения и умножения.
А это опять означает линейный рост объёма кода для покрытия более-менее всех интересных нам числовых типов.
В свете этого мне проще плюнуть и напилить умножение матриц через делегаты:
T[,] MatrixMultiply<T>(T[,] a, T[,] b, Func<T, T, T> mul, Func<T, T, T> add, T zero)
{
int rA = a.GetLength(0);
int cA = a.GetLength(1);
int rB = b.GetLength(0);
int cB = b.GetLength(1);
if (cA != rB)
throw new InvalidOperationException("Argument size mismatch");
double[,] result = new double[rA, cB];
for (int i = 0; i < rA; i++)
for (int j = 0; j < cB; j++)
{
T t = zero;
for (int k = 0; k < cA; k++)
t = add(t, mul(a[i, k], b[k, j]));
result [i, j] = temp;
}
return result;
}
Заодно этот код понятнее читается. Использовать его ничуть не сложнее шаманства с моноидами:
int[,] a;
int[,] b;
var c = MatrixMultiply(a, b, (x, y)=>x*y, (x, y)=>x+y, 0);
2. Вот екстеншны мне нравятся. Прежде всего возможностью напилить Extension property — потому что регулярно возникает желание напилить что-то вроде .Height или .Width для двумерных массивов, или Length для коллекций — по тем же правилам, что и обычные методы.
То есть если у нас тип умеет "родной" Length — как массив — то он и возвращается за O(1). А если нет — велкам в енумерцию, и не надо в каждом типе пилить реализацию.
И не надо вносить это в интерфейс как дефолтный метод.
И да — перегрузить оператор для чужого типа — тоже отличная идея.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>0. 90% из того, о чём он рассуждает — это сложный способ разрешить определять operator+ за пределами типа. S>В плюсах этой проблемы нет как класса благодаря наличию свободных функций; в шарпе этого нет — в итоге имеем печальный итог: мы не можем описать оператор+ для аргументов типа A и B, не будучи авторами A или B.
Проблема определить оператор сложения вне типа никак и ничем принципиально не отличается от проблемы определить оператор сравнения вне типа. Это ровно та же проблема для ровно тех же задач — написание обобщённых алгоритмов и структур данных, которые используют полиморфные сложения и сравнения.
Алгоритмы, использующие обобщённое сравнение всем известны: сортировки, деревья, хэш-таблицы. Использование компаратора ни у кого удивления не вызывает.
Алгоритмы, использующие обобщённое сложение никому кроме меня не известны: например, алгоритм Дейкстры на графах. Поэтому использование моноида вызывает бурю эмоций.
Для обобщённого сравнения ты реализуешь IComparer<T> или IEqualityComparer<T>. Аналогично, для обобщённого сложения (обобщённой бинарной операции) ты реализуешь IMonoid<T>. Или, если ты больше не инженер, а уже менеджер, то IGenericAggregationControllerManager<T>, так трудовому народу понятнее.
S>1. Рассказ про то, что у инта сразу два моноида, был убедителен, но непонятно, как именно это можно применить. Ну, кроме вырожденного случая "давайте проагрегируем массив интов с использованием 1 и * в качестве нашего моноида".
Давай не про инты, а про float'ы, ок? Аддитивный моноид понятно, например, складывать веса дуг вдоль пути на графе. Мультипликативный моноид, например, когда у тебя цепочка процентных ставок: начинаем с 1.0, заплатили налог 13% (домножить на 0.87), получили interest 5% (домножить на 1.05) и так далее.
Точно так же, как у тебя могут быть разные компараторы для одного типа (сравнение строк с учётом регистра, без учёта культуры, etc.), так и моноиды могут быть разные для одного типа.
Здравствуйте, Qbit86, Вы писали:
Q>Проблема определить оператор сложения вне типа никак и ничем принципиально не отличается от проблемы определить оператор сравнения вне типа. Это ровно та же проблема для ровно тех же задач — написание обобщённых алгоритмов и структур данных, которые используют полиморфные сложения и сравнения.
Q>Алгоритмы, использующие обобщённое сравнение всем известны: сортировки, деревья, хэш-таблицы. Использование компаратора ни у кого удивления не вызывает. Q>Алгоритмы, использующие обобщённое сложение никому кроме меня не известны: например, алгоритм Дейкстры на графах. Поэтому использование моноида вызывает бурю эмоций.
Q>Для обобщённого сравнения ты реализуешь IComparer<T> или IEqualityComparer<T>. Аналогично, для обобщённого сложения (обобщённой бинарной операции) ты реализуешь IMonoid<T>. Или, если ты больше не инженер, а уже менеджер, то IGenericAggregationControllerManager<T>, так трудовому народу понятнее.
В том-то и дело. Многие программисты могут представить зачем им понадобится обощенное сравнение. Даже если им на практике никогда не доводилось создаавать код, где такое срвнение нужно (у нас же есть .OrderBy).
Но практически никто не может представить зачем нужно обобщенное сложение. Такое мало того, что никто не писал, мало кто представляет зачем оно нужно хотя бы в теории.
Зато все понимают кост новой фичи языка — лишние ключевые слова и совместимость с существующим, более сложное чтение и навигация по коду. Неожиданные сайд-эффекты от лишнего юзинга.
Нужен другой пример использования ролей, который будет более понятен всем. Я думаю эта фича, как и куча последних фич, родилась из разработки самого компилятора C#. Могли бы сделать пример с ним, а не с моноидами.
_>>Ну так ты до этого не понял при чём там хипстеры и менеджеры Q>Так ведь это ж ты не понял, чем отличается монада от хламидомонады : )
Нет. Это ты сначала написал что не понял при чём там хипстеры и менеджеры и я объяснил. Потом ты не понял при чём там монады, и я объяснил. Сейчас ты решил что всё понял. И сразу про хламидомонады. "Можно я уже пойду?"
Q>Внезапно, код с моноидом в стиле как на видео будет быстрее, чем стандартная лапша с делегатами : )
Ну если для сложения и умножения надо писать стандартную лапшу с делегатами...
* Код выкинут за ненадобностью.
Все и так знают что в C# делегаты сделаны через одно место. Генерировали бы они при создании лямбды интерфейс в одним методом .Invoke (как это делает один всем известный язык), и оба подхода работали бы одинаково быстро.
Если мы про производительность в реальной жизни — то иногда случается что в стиле "у нас всё алгебраично" пишут алгоритм сложнее однострочника. Даже на твоём примере — допустим начали с суммы, а потом понадобилось ещё min и max посчитать. В императивном стиле найдут те 3 строчки кода, добавят ещё две и посчитают всё за один проход. В Моноидах знаешь что будет? Сделают ещё парочку моноидов, и вызовут их. И не потому что диверсанты, а просто логика подхода такая. При этом формально всё по фэншую, и сложность останется O(n). А по факту три прохода по памяти далеко не равны одному.
И через год продуктивной работы в таком стиле мы приходим к ситуации когда всё очень красиво. Но безбожно тормозит. И при этом какого-то конкретного боттлнека нету. Открываешь профайлер — а там в топах эти apply, reduce, и т.п. То один лишний проход по памяти, то два. И каждый раз спектр возможных оптимизаций сводятся к отказу от абстракций и объединению мелких кусков в более крупные, написанные в machine-friendly стиле (читай императивно).
В общем я это всё сразу и написал
. Но стоит в объяснении упустить какую-то промежуточную мелочь, и ты сразу решаешь что я что-то не понимаю. Всё наоборот. Когда я эту магию плохо понимал — недостатков вообще не видел, кругом виделись одни сплошные плюсы.
Здравствуйте, hi_octane, Вы писали:
_>Нет. Это ты сначала написал что не понял при чём там хипстеры и менеджеры и я объяснил. Потом ты не понял при чём там монады, и я объяснил.
А я объяснил, почему твоё объяснение некорректное. Про straw man не только к AlexRK относилось, но и к тебе в первую очередь; почитай, пожалуйста.
_>Сейчас ты решил что всё понял.
Да, я всё понял: ты слышал звон, но не знаешь, где он. M-word — эта сложна! Нипанятна! А, речь не про этот M-word?.. Всё равно от лукавого, буквы-то те же! Нафиг не нужон моноид ваш!
Q>>Внезапно, код с моноидом в стиле как на видео будет быстрее, чем стандартная лапша с делегатами : )
Хоть по этому вопросу возражений нет.
_>Если мы про производительность в реальной жизни — то иногда случается что в стиле "у нас всё алгебраично" пишут алгоритм сложнее однострочника. Даже на твоём примере — допустим начали с суммы, а потом понадобилось ещё min и max посчитать. В императивном стиле найдут те 3 строчки кода, добавят ещё две и посчитают всё за один проход. В Моноидах знаешь что будет? Сделают ещё парочку моноидов, и вызовут их. И не потому что диверсанты, а просто логика подхода такая. При этом формально всё по фэншую, и сложность останется O(n). А по факту три прохода по памяти далеко не равны одному. _>И через год продуктивной работы в таком стиле мы приходим к ситуации когда всё очень красиво. Но безбожно тормозит. И при этом какого-то конкретного боттлнека нету. Открываешь профайлер — а там в топах эти apply, reduce, и т.п. То один лишний проход по памяти, то два. И каждый раз спектр возможных оптимизаций сводятся к отказу от абстракций и объединению мелких кусков в более крупные, написанные в machine-friendly стиле (читай императивно).
Если при использовании IMonoid<T> непременно такие сложности возникнут, то они же неизбежно должны возникнуть и при использовании интерфейса IEqualityComparer<T>? Сценарии использования у них ведь строго одинаковые.
Или ты из тех же соображений отказался от IEqualityComparer<T>?
interface IMonoid<T>
{
static T Zero{get;}
static T operator+(T t1, T t2)
static T operator*(T t1, T t2)
}
S>var c = MatrixMultiply<IntMulMonoid, IntAddMonoid, int>(a, b)
Я вот только не понял зачем IntMulMonoid, IntAddMonoid
role IntMonoid extednds int
{
public static int Zero = 0;
}
и достаточно всего
Ну или если результат не int добавить функцию приведения добавить в интерфейс
static implicit operator <R> (T value);
И вызов соотвественно
var c = MatrixMultiply<IntMulMonoid,double>(a, b)
и солнце б утром не вставало, когда бы не было меня
Здравствуйте, gandjustas, Вы писали:
G>Но практически никто не может представить зачем нужно обобщенное сложение. Такое мало того, что никто не писал, мало кто представляет зачем оно нужно хотя бы в теории.
Но подожди, есть же уже известный пример такого обобщённого алгоритма — Linq Aggregate(). Он внутри использует обобщённую бинарную операцию. Не бином Ньютона!
G>Зато все понимают кост новой фичи языка — лишние ключевые слова и совместимость с существующим, более сложное чтение и навигация по коду. Неожиданные сайд-эффекты от лишнего юзинга.
Полезность новой фичи я не обсуждал. Меня триггернул тезис с позиции wannabe-прагматика: «моноиды ваши заумные не нужны, потому что монады это сложно»
G>Нужен другой пример использования ролей, который будет более понятен всем. Я думаю эта фича, как и куча последних фич, родилась из разработки самого компилятора C#. Могли бы сделать пример с ним, а не с моноидами.
Я бы тоже делал аналогичную презентцию с моноидом по умолчанию — просто мне это кажется самым простым и инструктивным примером (безотносительно видео Мэдса, это частый пример). Но сегодняшний тред пошатнул мою веру в рациональность аудитории. Так что в виду моды на M-фобию можно было заменить IMonoid<T> на IEqualityComparer<T> — всё то же самое, только без страха, что вот-вот сотни вложенных bind/apply заполонят код и сожрут всю память, гроб, отладка, кладбище, монада.
Здравствуйте, Qbit86, Вы писали:
Q>Алгоритмы, использующие обобщённое сравнение всем известны: сортировки, деревья, хэш-таблицы. Использование компаратора ни у кого удивления не вызывает. Q>Алгоритмы, использующие обобщённое сложение никому кроме меня не известны: например, алгоритм Дейкстры на графах. Поэтому использование моноида вызывает бурю эмоций.
Не, никакой бури не вызывает. То же самое матричное умножение вполне можно использовать для вычисления транзитивного замыкания на матрице смежности, если * заменить на &&, а + — на ||.
Или для поиска кратчайших путей — тот же Дийкстра, только для всех пар вершин сразу.
Q>Для обобщённого сравнения ты реализуешь IComparer<T> или IEqualityComparer<T>. Аналогично, для обобщённого сложения (обобщённой бинарной операции) ты реализуешь IMonoid<T>. Или, если ты больше не инженер, а уже менеджер, то IGenericAggregationControllerManager<T>, так трудовому народу понятнее.
Это всё как раз понятно.
S>>1. Рассказ про то, что у инта сразу два моноида, был убедителен, но непонятно, как именно это можно применить. Ну, кроме вырожденного случая "давайте проагрегируем массив интов с использованием 1 и * в качестве нашего моноида".
Q>Давай не про инты, а про float'ы, ок? Аддитивный моноид понятно, например, складывать веса дуг вдоль пути на графе. Мультипликативный моноид, например, когда у тебя цепочка процентных ставок: начинаем с 1.0, заплатили налог 13% (домножить на 0.87), получили interest 5% (домножить на 1.05) и так далее.
А какая разница? Хоть decimal. Ничто не мешает нам строить моноид хоть над string — в нём же даже коммутативность не нужна.
Я говорю о том, что предлагаемый вариант ролей не шибко помогает.
Q>Точно так же, как у тебя могут быть разные компараторы для одного типа (сравнение строк с учётом регистра, без учёта культуры, etc.), так и моноиды могут быть разные для одного типа.
Q>Взя затея вокруг шейпов — это добавить чуть синтаксического сахара вокруг всех этих IEqualityComparer<T>'ов и IMonoid<T>'ов, чтоб передавать не инстансы (они ж все одинаковые stateless), а сами типы.
Рулится прямо сейчас:
interface IMonoid<T>
{
T Zero {get;}
T Add(T t1, T t2)
}
public class Add<M>
where M: IMonoid<T>, new
{
public static T All<T>(T[] ts)
{
var m = new M();
var T result = m.Zero;
foreach(vat t in ts)
result = m.Add(result, t);
}
}
class IntAddMonoid: IMonoid<int>
{
public int Zero{get=>0;}
public int Add(int t1, int t2)=>t1+t2;
}
int sixtyThree = Add<IntAddMonoid>.All(new[]{1, 2, 4, 8, 16, 32});
С учётом того, что статические интерфейсы ты ничем не параметризуешь, дефолтного конструктора вполне достаточно.
Вся разница — код работает прямо сейчас, не дожидаясь C# 12.
Что было бы ценно — возможность биндить моноиды, кольца и группы к типам в обобщённом коде без костылей.
Чего мы хотим? Чтобы AddAll просто работал просто из коробки, с любыми Numeric-типами, включая Complex и прочую экзотику. Вот extension для этого — отличная идея.
А с ролями — не вижу смысла. Чтобы можно было использовать AddAll прямо в таком виде для того, чтобы вычислять минимум ряда, передав Min(a, b) в качестве +?
Простите, не вижу ценности по сравнению с простым кодом выше. Ну, ок — если добавить статические интерфейсы, то можно будет убрать строчку var m = new M(), и просто обращаться к M.Zero и M.Add.
С моей точки зрения, для обобщённой операции сложения использовать оператор "+" — баловство.
Операторы удобны там, где есть более-менее сложные формулы. Ну, там (a[i-1]+2*a[i]+a[i+1])/4. Вот такое переписывать в стиле "вызов метода" — плохо, теряется читаемость.
И как раз такие формулы имеют смысл для обобщённых numeric типов. И как раз в них сложения и умножения — это сложения и умножения, а не "обобщённые сложения".
Уйдемте отсюда, Румата! У вас слишком богатые погреба.