簡體   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