【问题标题】:Removing code duplication删除重复代码
【发布时间】:2011-04-12 04:17:08
【问题描述】:

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

例如这里是map函数对Lists和Sets的实现:

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

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

编辑:

Func1是一个接口定义为:

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

【问题讨论】:

  • 看起来您可以只使用Collection 接口,以消除ListSet 接口的不同情况。
  • @Bears:问题是这样的:map for List 应该返回 Listmap for Set 应该返回 Set 等等。
  • 因此,使用ListSet 作为参数实现Collection,并从ListSet 便利类中调用该实现。

标签: java generics programming-languages functional-programming


【解决方案1】:

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

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

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

【讨论】:

  • 感谢 Johannes 的回复,但我在这里没有使用任何功能数据结构。 ListSet 在我的示例中分别是 java.util.Listjava.util.Set
  • 当然,但是这些实现了类似 IEnumerable 或 ICollection 的东西(在这种情况下是集合单子)
  • @Johannes:C# 也有同样的问题。他们的解决方案是放弃并始终返回 IEnumerable。
【解决方案2】:
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;
    }
}

【讨论】:

  • API相同,新的map方法是私有的
  • 还是有很多代码重复。对于我可能想要添加的每个新方法,我需要使用Collections 编写私有实现,然后为每种数据类型编写一个方便的方法。来吧,必须有更好的方法来做到这一点。 :(
  • @one-zero-zero-one 您需要一个带有通用代码的方法和一个决定使用哪个实现的方法,是的。您可以只使用实现方法。你可以使用继承,但对于这些静态方法,我觉得这很不愉快。
【解决方案3】:

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

要了解更多信息,请查看Traversable 和使用它的东西。我相信CanBuildFrom 是许多魔法发生的地方。

【讨论】:

    【解决方案4】:

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

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

    上述论文的两张截图:



    【讨论】:

      【解决方案5】:

      Java 没有高阶多态性(也称为高阶多态),因此这在类型系统中是不可能的。许多 Java 程序员求助于 XML 和/或反射(即转义类型系统)来解决这个缺陷。

      Scala 可以处理这个问题,您所描述的称为协变函子。这种相当基本的数据类型(以及更多)已在 Scalaz 库中实现,并包括 java.util.* 的实现。

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

      如果您想进一步探索这个特定概念,您可能希望在 Google 上搜索“20 个中级 Scala 练习”。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2013-03-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-10-19
        相关资源
        最近更新 更多