繁体   English   中英

我该如何在C#中制作类似Monoid的界面?

[英]How can I make a monoid-like interface in C#?

我想要求实现接口(或从类派生)的事物包含Aggregate的实现。 也就是说,如果它们是T类型,我希望它们具有Func<T,T,T> 在Haskell中,这称为“ monoid”。

编辑 :我想打电话是这样的:

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

根据DigalD的回答,这是我的最佳尝试,但无法编译:

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

Monoid是关联操作以及该操作的标识。

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

一个monoid的契约是对于所有abc

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

这个版本呢:

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

Apocalisp的答案看起来最接近分数,但我更喜欢这样的东西:

public interface IMonoid<T>
{
    T Combine(T x, T y);
    T Identity { get; }
}

虽然Haskell称呼monoid 身份为 mempty ,但我认为使用抽象代数的语言更为合理,所以我将身份值命名为Identity 同样,相对于Haskell的mappend ,我更喜欢“ Combinemappend ,因为“ append ”一词似乎表示某种形式的“列表追加”操作,它根本不需要。 然而,“ Combine也不是一个完美的词,因为第一个最后一个等分线词都不能组合值。 相反,他们忽略其中之一。 我愿意为二进制操作起一个更好的名字的建议...

(在顺便说一下,在Haskell,顺便说一句,我更喜欢使用<>运算符别名而不是mappend函数,这样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身份(在本例中为m.Identity开始累积。

正如我在文章Semigroups accumulate中描述的那样,您可以使用Aggregate扩展方法,而不是强制性的for循环,但是您必须使用采用初始种子值的重载。 那种子的价值是m.Identity

现在,您可以定义各种monoid,例如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参数IMonoid<T> Concat方法的this参数,因此该方法扩展了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()new Product()对象,则可以使您的单面体成为Singletons ,就像这样的All monoid:

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 monoid:

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

我将其保留为练习,以使读者了解如何实施Any

您不应该只使接口也通用吗?

interface IMonoid<T>
{
    public IMonoidHandler<T> handler {get;set;}
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM