我们能有一种语言来隐藏我们收集的东西吗?

511 阅读3分钟

我刚刚修复了一个错误。这个修正要求我用每种类型的初始值来初始化一个Object[] 数组,而不是只用null ,即false ,用于boolean0 ,用于int0.0 ,用于double ,等等。所以,不要只做:

Object[] converted = new Object[parameterTypes.length];

我需要:

Object[] converted = new Object[parameterTypes.length];

for (int i = 0; i < converted.length; i++)
    converted[i] = Reflect.initValue(parameterTypes[i]);

在主观上第8E17次,我写了一个循环。这个循环除了为循环结构中的每个元素调用一个方法外,没有做任何有趣的事情。我感受到了我们的朋友Murtaugh的痛苦

为什么我们要区分T和T[]?

我真正想做的是这个。我有一个方法Reflect.initValue()

public static <T> T initValue(Class<T> type) {}

我真正想做的是这个,以这样或那样的方式:

converted = initValue(parameterTypes);

(是的,有一些细微的问题需要考虑,比如这应该是启动一个数组还是给一个数组赋值。现在先不考虑这些。重点是,没有人喜欢写循环。也没有人喜欢写map/flatMap:

Stream.of(parameterTypes)
      .map(Reflect::initValue)
      .toArray(converted);

它是那么多无用的、重复的、基础性的仪式,我不喜欢写也不喜欢读。我的 "商业逻辑 "在这里只是:

converted = initValue(parameterTypes);

我有3个元素:

  • 一个源数据结构parameterTypes
  • 一个目标数据结构converted
  • 一个映射函数initValue

这就是我在代码中应该看到的一切。所有关于如何迭代的基础设施都是完全没有意义的,而且很无聊。

SQL连接

事实上,SQL连接往往是这样的。我们使用主键/外键关系,所以父表和子表之间的路径在大多数情况下是非常明显的。连接很酷,关系代数也很酷,但在大多数情况下,它只是妨碍了编写可理解的业务逻辑。在我看来,这是Hibernate最大的创新之一(可能其他人也这样做了,也许甚至在Hibernate之前):隐式连接,jOOQ也复制了这一点。 写这个有很多仪式:

SELECT
  cu.first_name,
  cu.last_name,
  co.country
FROM customer AS cu
JOIN address USING (address_id)
JOIN city USING (city_id)
JOIN country AS co USING (country_id)

当这种替代的、直观的语法会更方便:

SELECT
  cu.first_name,
  cu.last_name,
  cu.address.city.country.country
FROM customer AS cu

隐式连接语法的意思是什么,马上就清楚了。写显式连接的语法仪式是不必要的。 同样,连接真的很酷,强大的用户在需要时可以使用它们。例如,偶尔的NATURAL FULL OUTER JOIN还是可以做的!但是让我们承认,80%的连接都很无聊,可以用上述的语法糖来代替。

对Java的建议

当然,这个建议并不完美,因为它没有处理在一个古老的语言中引入这样一个重要功能的无数边缘案例。但是,如果我们允许自己专注于大局,如果我们能做到这一点,岂不是很好。

class Author {
  String firstName;
  String lastName;
  Book[] books; // Or use any collection type here
}

class Book {
  String title;
}

然后:

Author[] authors = ...

// This...
String[] firstNames = authors.firstName;

// ...is sugar for this (oh how it hurts to type this):
String[] firstNames = new String[authors.length];
for (int i = 0; i < firstNames.length; i++)
    firstNames[i] = authors[i].firstName;

// And this...
int[] firstNameLengths = authors.firstName.length()

// ... is sugar for this:
int[] firstNameLengths = new int[authors.length];
for (int i = 0; i < firstNames.length; i++)
    firstNames[i] = authors[i].firstName.length();

// ... or even this, who cares (hurts to type even more):
int[] firstNameLengths = Stream
  .of(authors)
  .map(a -> a.firstName)
  .mapToInt(String::length)
  .toArray();

忽略数组的用法,它也可以是一个ListStreamIterable ,不管是什么数据结构或语法,都可以从1的算数到N的算数。 或者得到一组作者的书:

Author[] authors = ...
Book[] books = authors.books;

除了这个意思,还能有什么别的意思吗:

Stream.of(authors)
      .flatMap(a -> Stream.of(a.books))
      .toArray(Book[]::new);

为什么我们要不断地把这些东西拼出来?它们不是商业逻辑,它们是无意义的、无聊的、基础设施。虽然是的,肯定有很多边缘案例(如果编译器不能弄清楚如何从A到B,我们可以忍受偶尔的编译器错误),但也有很多 "非常明显 "的案例,其中证明性的映射逻辑(命令式或功能式,并不重要)就是完全明显和无聊的。 但是它妨碍了写作和阅读,尽管在很多情况下看起来很明显,但还是很容易出错!我认为是时候重新审视APL背后的想法了,在APL中,所有东西都是数组,因此,对arity 1类型的操作同样可以应用于arity N类型,因为这种区别往往不是很有用。