5

我想要求实现接口(或从类派生)的东西包含一个实现Aggregate。也就是说,如果它们是 typeT我希望他们有一些 type Func<T,T,T>。在 Haskell 中,这被称为“monoid”。

编辑:我想调用的是这样的:

list.Aggregate((x, accum) => accump.MAppend(x));

根据 DigialD 的回答,这是我最好的尝试,但它没有编译:

interface IMonoid<T>
{
    T MAppend(T other);
}

class Test
{
    public static void runTest<T>(IEnumerable<IMonoid<T>> list)
    {
        // doesn't work
        list.Aggregate((x, ac) => ac.MAppend(x));
    }
}
4

4 回答 4

3

一个幺半群是一个关联操作以及该操作的标识。

interface Monoid<T> {
  T MAppend(T t1, T t2);
  T MEmpty
}

一个幺半群的契约是对所有a,b和的契约c

  1. 关联性:MAppend(Mappend(a, b), c)=MAppend(a, Mappend(b, c))
  2. 左身份:MAppend(MEmpty, a)=a
  3. 正确的身份:MAppend(a, MEmpty)=a

您可以使用它来添加列表中的元素:

class Test {
  public static T runTest<T>(IEnumerable<T> list, Monoid<T> m) {
    list.Aggregate(m.MEmpty, (a, b) => m.MAppend(a, b));
  }
}
于 2013-10-16T22:24:53.143 回答
3

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 });

这里,atrue。您可以以相同的方式使用类似编写Any的幺半群:

var a = Any.Instance.Concat(new[] { false, true, false });

我将把它作为练习留给读者了解如何Any实现。

于 2018-01-29T12:55:19.287 回答
0

这个版本怎么样:

interface IMonoid<T>
{
    T MAppend(IMonoid<T> other);
}

class Test
{
    public static void runTest<T>(IEnumerable<IMonoid<T>> list)
        where T : IMonoid<T>
    {
        list.Aggregate((x, ac) => ac.MAppend(x));
    }
}

或者更好的是,从一开始就强制执行:

interface IMonoid<T>
    where T : IMonoid<T>
{
    T MAppend(IMonoid<T> other);
}
于 2013-10-16T22:07:28.400 回答
-1

您不应该也将接口设为通用吗?

interface IMonoid<T>
{
    public IMonoidHandler<T> handler {get;set;}
}
于 2013-10-16T20:17:04.590 回答