9

我正在嘗試爲Java創建一個小函數編程庫(只是爲了讓我自己癢)。雖然定義higher-order functionsList s,Set s和Map s我遇到過這個問題:採用集合並返回相同類型集合的函數具有幾乎相同的實現,但必須重新定義每個函數數據結構 - List s,Set s和Map s。刪除代碼重複

例如,這裏是map功能的List秒,Set S中的實現:

public static <A, B> List<B> map(
    List<? extends A> xs, 
    Func1<? super A, ? extends B> transformer 
) { 
    List<B> ys = new ArrayList<B>(); 
    for(A a : xs) { 
    ys.add(transformer.apply(a)); 
    } 
    return ys; 
} 

public static <A, B> Set<B> map(
    Set<? extends A> xs, 
    Func1<? super A, ? extends B> transformer 
) { 
    Set<B> ys = new HashSet<B>(); 
    for(A a : xs) { 
    ys.add(transformer.apply(a)); 
    } 
    return ys; 
} 

一個filter功能:

public static <A> List<A> filter(
    List<? extends A> xs, 
    Func1<? super A, Boolean> predicate 
) { 
    List<A> ys = new ArrayList<A>(); 
    for(A a : xs) { 
    if(predicate.apply(a)) { 
     ys.add(a); 
    } 
    } 
    return ys; 
} 

public static <A> Set<A> filter(
    Set<? extends A> xs, 
    Func1<? super A, Boolean> predicate 
) { 
    Set<A> ys = new HashSet<A>(); 
    for(A a : xs) { 
    if(predicate.apply(a)) { 
     ys.add(a); 
    } 
    } 
    return ys; 
} 

。從這個例子可以看出,該機構SetList的實現幾乎相同。

有喜歡在我的圖書館mapfilter很多很多的功能,每一類又被定義三次爲每種類型的收藏我感興趣的(即ListSet,並Map)。這導致了很多代碼重複和代碼異味。我想知道在Java中是否有某種方法可以幫助我避免所有的代碼重複。

任何幫助將不勝感激。謝謝。

編輯:

Func1是接口定義爲:

interface Func1<A, B> { 
    public B apply(A a); 
} 
+0

它看起來像你可以只使用'集合'接口,以消除'List'和'Set'接口的單獨情況。 – 2010-09-14 13:35:23

+0

@熊:問題是這樣的:'List'的map應該返回'List','Set'的'map'應該返回一個'Set'等。 – 2010-09-14 13:45:07

+0

因此,以'List'或'Set'作爲參數實現'Collection',並從'List'和'Set'方便類中調用該實現。 – rsp 2010-09-14 13:53:55

回答

4

Java沒有高階多態性(又名高種),所以這在類型系統中是不可能的。許多Java程序員訴諸XML和/或反射(即逃避類型系統)來解決這個缺陷。

Scala可以處理這個問題,你所描述的稱爲協變函子。這個相當基礎的數據類型(以及更多)已經在Scalaz庫中實現,幷包含java.util。*的實現。

此外,還有更多的協變函子不是集合,也有更多的函子不是協變的。

如果你想進一步探索這個特定的概念,你可能希望谷歌的「20中級斯卡拉練習」。

1

有效的列表僅僅是一個單子對於類型T,給它以存儲類型的多個實例的能力。這就是爲什麼所有通常的monad法則適用於此的原因,因此您可以使用bindreturn成員執行所有操作。

對不起,我現在沒有時間進一步解釋,但在.NET空間中,我們有SelectMany和Enumerable.Repeat(1,element)用於相同的目的。有很多關於這方面的信息。

可以使用SelectMay分別綁定來實現任何運算符(例如您的示例中的filter)。

+0

感謝Johannes的迴應,但我沒有在這裏使用任何功能數據結構。我的例子中'List'和'Set'分別是'java.util.List'和'java.util.Set'。 – 2010-09-14 13:46:37

+0

當然,但這些實現類似IEnumerable或ICollection(在這種情況下收集單子) – 2010-09-14 14:03:03

+0

你可以添加一些代碼來解釋你的觀點嗎? – 2010-09-14 16:34:06

6
public static <A, B> List<B> map(
    List<? extends A> xs, 
    Func1<? super A, ? extends B> transformer 
) { 
    List<B> ys = new ArrayList<B>(); 
    map(xy, transformer, ys); 
    return ys; 
} 

public static <A, B> Set<B> map(
    Set<? extends A> xs, 
    Func1<? super A, ? extends B> transformer 
) { 
    Set<B> ys = new HashSet<B>(); 
    map(xy, transformer, ys); 
    return ys; 
} 
private static <A, B> map(
    Collection<? extends A> xs, 
    Func1<? super A, ? extends B> transformer, 
    Iterable<B> ys 
) { 
    for(A a : xs) { 
    ys.add(transformer.apply(a)); 
    } 
} 

工作完成。

注意,這是典型的Java API,可以將可變集合傳入,而不是在該方法中創建新集合。就我個人而言,我不是集合級別的可變性迷,但它是我們必須使用的(Java)。

(我不喜歡AB作爲這類東西的通用參數。)

或者你可以使用一個工廠。

public static <A, B> List<B> map(
    List<? extends A> xs, 
    Func1<? super A, ? extends B> transformer 
) { 
    return map(xs, transformer, new CollectionFactory<B, List<B>>() { 
     public List<B> create() { return new ArrayList<B>(); } 
    }); 
} 

public static <A, B> Set<B> map(
    Set<? extends A> xs, 
    Func1<? super A, ? extends B> transformer 
) { 
    return map(xs, transformer, new CollectionFactory<B, Set<B>>() { 
     public Set<B> create() { return new HashSet<B>(); } 
    }); 
} 

private interface CollectionFactory<E, C extends Collection<E>> { 
    C create(); 
} 

private static <A, B, C extends Collection<B>> C map(
    Iterable<? extends A> xs, 
    Func1<? super A, ? extends B> transformer, 
    CollectionFactory<B, C> factory 
) { 
    C ys = factory.create(); 
    for(A a : xs) { 
    ys.add(transformer.apply(a)); 
    } 
    return ys; 
} 

(如果你可以忍受匿名內部類的毫無意義的冗長)

如果不是因爲Collection,那麼你會需要把一些(醜陋的)適配器

爲了完整(雖然沒有測試過,可以用一些調整做),不愉快的解決方案使用繼承:

Set<String> strs = hashSets().map(things, formatter); 

... 

public static <E> Functions<E, Set<E>> hashSets() { 
    return new Functions<E, Set<E>>() { 
     protected Set<E> createCollections() { 
      return new HashSet<E>(); 
     } 
    }; 
} 

public abstract class Functions<E, C extends Collection<E>> { 
    protected abstract C createCollection(); 

    public <S> C map(
     Set<? extends S> xs, 
     Func1<? super S, ? extends E> transformer 
    ) { 
     C ys = createCollection(); 
     for(S a : xs) { 
     ys.add(transformer.apply(a)); 
     } 
     return ys; 
    } 

    public <S> C filter(
     List<? extends S> xs, 
     Func1<? super S, Boolean> predicate // Predicate<? super S> might be nicer!! 
    ) { 
     C ys = createCollection(); 
     for(A a : xs) { 
     if(predicate.apply(a)) { 
      ys.add(a); 
     } 
     } 
     return ys; 
    } 
} 
+0

API是一樣的,新的地圖方法是私人的 – 2010-09-14 13:52:41

+0

它仍然是很多代碼重複。對於我想要添加的每個新方法,我需要使用'Collections'編寫私有實現,然後爲每種數據類型編寫一個便捷方法。來吧,必須有更好的方式來做到這一點。 :( – 2010-09-14 16:35:52

+0

@ one-zero-zero-one你需要一個具有公共代碼和方法的方法來決定使用哪個實現,你可以使用實現方法,你可以使用繼承,但是對於這些類型的靜態方法,我會叫那個不愉快的。 – 2010-09-14 17:30:13

2

我不相信Java的類型系統足夠複雜來解決這個問題,但是Scala的是。使用2.8版本的集合庫時,他們構建了一個系統,以根據您正在使用的集合自動創建適當類型的集合。因此,如果您撥打List撥打filter,它將返回一個新的List。致電filterSet,你會得到一個Set回來。它這樣做,但仍然只有一個執行filter

要了解更多信息,請查看Traversable以及使用它的內容。我相信CanBuildFrom是很多魔術發生的地方。

4

我認爲你可以做得比湯姆在his answer中建議的要好。 Java不支持更高版本的類型 - 這個功能可以幫助您對集合類型進行抽象,從而避免爲每個集合類型重複相同的代碼。

Scala支持此功能,並且廣泛用於其標準庫。 Adriaan Moors的This paper討論了Scala如何通過更高級的類型避免這種代碼重複。

二是從上述文件截圖:


alt text


alt text

+2

同意。湯姆(上圖)是不正確的。 – 2010-09-16 01:43:05