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