如何在C#中创建类似monoid的界面?

时间:2013-10-16 20:09:45

标签: c# functional-programming

我想要求实现接口(或从类派生)的东西包含{​​{1}}的实现。也就是说,如果它们属于Aggregate类型,我希望它们具有类型T的类型。在Haskell中,这被称为“幺半群”。

编辑:我想要的是这样的:

Func<T,T,T>

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

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

4 个答案:

答案 0 :(得分:3)

monoid是一个关联操作以及该操作的标识。

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

幺半群的合同适用于所有abc

  1. 相关性:MAppend(Mappend(a, b), c) = MAppend(a, Mappend(b, c))
  2. 左侧身份:MAppend(MEmpty, a) = a
  3. 正确的身份:MAppend(a, MEmpty) = a
  4. 您可以使用它来添加列表中的元素:

    class Test {
      public static T runTest<T>(IEnumerable<T> list, Monoid<T> m) {
        list.Aggregate(m.MEmpty, (a, b) => m.MAppend(a, b));
      }
    }
    

答案 1 :(得分: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);
}

答案 2 :(得分:0)

Apocalisp的答案看起来最接近标记,但我更喜欢这样的事情:

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

当Haskell调用monoid 身份 mempty时,我认为使用抽象代数的语言更合理,所以我将身份值命名为Identity。同样,我更喜欢术语Combine而不是Haskell mappend,因为 append 这个词似乎表示某种列表追加操作,它根本不需要。然而,Combine也不是一个完美的词,因为 first last monoid都没有组合这些值;相反,他们忽略了其中一个。我愿意接受二进制操作更好名称的建议......

(在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 accumulate中所描述的那样,人们总是必须以幺半群身份开始积累,在这种情况下m.Identity

正如我在文章Semigroups accumulate中所描述的那样,您可以使用for扩展方法而不是命令式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()new Product()个对象,则可以使用All monoid来生成monoid Singletons

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是如何实现的。

答案 3 :(得分:-1)

你不应该只使接口通用吗?

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