删除代码重复

时间:2010-09-14 13:27:19

标签: java generics programming-languages functional-programming

我正在尝试为Java创建一个小函数式编程库(只是为了抓住我自己的痒)。在为ListSetMap定义higher-order functions时,我遇到了这个问题:收集集合并返回相同类型集合的函数具有几乎相同的实现,但必须为每个数据结构重新定义 - List s,SetMap s。

例如,以下是mapList s的Set函数的实现:

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,每个函数都为我感兴趣的每种类型的集合定义了三次(即List,{{ 1}}和Set)。这会导致很多代码重复和代码异味。我想知道Java中是否有某种方法可以帮助我避免所有的代码重复。

任何帮助将不胜感激。感谢。

修改

Map是一个定义为:

的接口
Func1

5 个答案:

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

答案 1 :(得分:4)

我认为你不能比汤姆在his answer中建议的做得更好。 Java不支持更高级的类型 - 一种可以帮助您抽象集合类型的功能,从而避免为每个集合类型复制相同的代码。

Scala支持此功能,并且在其标准库中广泛使用。 Adriaan Moors的This paper讨论了Scala如何在更高级别的类型的帮助下避免这种代码重复。

上述论文的两个截图:


alt text


alt text

答案 2 :(得分:4)

Java没有高阶多态(也就是更高类型),因此在类型系统中这是不可能的。许多Java程序员采用XML和/或反射(即逃避类型系统)来解决这个缺陷。

Scala可以解决这个问题,你所描述的内容称为协变函子。这个相当基础的数据类型(以及更多)已在Scalaz库中实现,并包含java.util的实现。*。

此外,还有更多的协变仿函数不是集合,还有更多仿函数不协变。

如果您希望进一步探索这一特定概念,您可能希望谷歌参加“20中级Scala练习”。

答案 3 :(得分:2)

我不相信Java的类型系统足够复杂以解决这个问题,但Scala是。使用2.8版本的集合库,他们构建了一个系统,可根据您正在使用的集合自动创建适当类型的集合。因此,如果您在filter上致电List,则会返回新的List。在filter上致电Set,您将获得Set。这样做虽然仍然只有filter的单个实现。

要了解详情,请查看Traversable以及使用它的内容。我相信CanBuildFrom是很多魔法发生的地方。

答案 4 :(得分:1)

有效地,列表只是类型T的Monad,使其能够存储该类型的多个实例。这就是为什么monad的所有常用法则都适用于此的原因,因此您可以使用bindreturn成员实现所有操作。

对不起,我现在没有时间进一步解释,但在.NET空间中我们有SelectMany和Enumerable.Repeat(1,element)用于相同的目的。有很多关于此的信息。

任何运算符(例如示例中的filter)都可以使用SelectMay分别实现绑定。