Здравствуйте, 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). А если нет — велкам в енумерцию, и не надо в каждом типе пилить реализацию.
И не надо вносить это в интерфейс как дефолтный метод.
И да — перегрузить оператор для чужого типа — тоже отличная идея.