Apocalisp 的答案看起来最接近标记,但我更喜欢这样的东西:
public interface IMonoid<T>
{
T Combine(T x, T y);
T Identity { get; }
}
虽然 Haskell 称 monoid identity mempty
,但我认为使用抽象代数的语言更合理,所以我命名为 identity value Identity
。同样,我更喜欢这个词Combine
而不是 Haskell 的mappend
,因为append这个词似乎表示某种列表追加操作,它根本不需要。Combine
然而,也不是一个完美的词,因为第一个和最后一个幺半群都没有组合这些值;相反,他们忽略了其中之一。我愿意为二进制操作提供更好的名称的建议......
(顺便说一句,在 Haskell 中,我更喜欢使用<>
运算符别名而不是mappend
函数,这样就可以避开命名问题......)
使用上面的IMonoid<T>
接口,您现在可以编写一个扩展方法,如下所示:
public static class Monoid
{
public static T Concat<T>(this IMonoid<T> m, IEnumerable<T> values)
{
return values.Aggregate(m.Identity, (acc, x) => m.Combine(acc, x));
}
}
在这里,我完全武断地和不一致地决定采用 Haskell 的命名,所以我将方法命名为Concat
.
正如我在我的文章Monoids 累积中所描述的,在这种情况下,总是必须以幺半群身份开始累积m.Identity
。
正如我在文章Semigroupsfor
中所描述的那样,您可以使用扩展方法而不是命令式循环,Aggregate
但您必须使用采用初始种子值的重载。该种子值为m.Identity
.
您现在可以定义各种幺半群,例如Sum
:
public class Sum : IMonoid<int>
{
public int Combine(int x, int y)
{
return x + y;
}
public int Identity
{
get { return 0; }
}
}
或Product
:
public class Product : IMonoid<int>
{
public int Combine(int x, int y)
{
return x * y;
}
public int Identity
{
get { return 1; }
}
}
由于我将 monoid 参数作为方法的this
参数,因此Concat
该方法扩展了IMonoid<T>
接口,而不是IEnumerable<T>
. 我认为这为您提供了更具可读性的 API。例如:
var s = new Sum().Concat(new[] { 1000, 300, 30, 7 });
产生s == 1337
,而
var p = new Product().Concat(new[] { 2, 3, 7 });
产生p == 42
.
如果你不喜欢每次都创建一个new Sum()
或对象,你可以让你的幺半群Singletons,像这个幺半群:new Product()
All
public class All : IMonoid<bool>
{
public static All Instance = new All();
private All() { }
public bool Combine(bool x, bool y)
{
return x && y;
}
public bool Identity
{
get { return true; }
}
}
您可以像这样使用它:
var a = All.Instance.Concat(new[] { true, true, true });
这里,a
是true
。您可以以相同的方式使用类似编写Any
的幺半群:
var a = Any.Instance.Concat(new[] { false, true, false });
我将把它作为练习留给读者了解如何Any
实现。