Could we Have a Language That Hides Collections From Us?

I just fixed a bug. The fix required me to initialise an Object[] array with the init values for each type, instead of just null, i.e. false for boolean, 0 for int, 0.0 for double, etc. So, instead of just doing:

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

I needed:

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

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

For the subjective 8E17th time, I wrote a loop. A loop that did nothing interesting other than call a method for each of the looped structure’s elements. And I felt the pain of our friend Murtaugh

Why do we distinguish between T and T[]?

What I really wanted to do is this. I have a method Reflect.initValue()

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

What I really want to do is this, in one way or another:

converted = initValue(parameterTypes);

(Yes, there are subtleties that need to be thought about, such as should this init an array or assign values to an array. Forget about them for now. Big picture first). The point is, no one enjoys writing loops. No one enjoys writing map/flatMap either:

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

It’s so much useless, repetitive, infrastructural ceremony that I don’t enjoy writing nor reading. My “business logic” here is simply

converted = initValue(parameterTypes);

I have 3 elements:
  • A source data structure parameterTypes
  • A target data structure converted
  • A mapping function initValue
That’s all I should be seeing in my code. All the infrastructure of how to iterate is completely meaningless and boring.

SQL joins

In fact, SQL joins are often the same. We use primary key / foreign key relationships, so the path between parent and child tables is very obvious in most cases. Joins are cool, relational algebra is cool, but in most cases, it just gets in the way of writing understandable business logic. In my opinion, this is one of Hibernate’s biggest innovations (probably others did this too, perhaps even before Hibernate): implicit joins, which jOOQ copied. There’s much ceremony in writing this:

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)

When this alternative, intuitive syntax would be much more convenient:

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

It is immediately clear what is meant by the implicit join syntax. The syntactic ceremony of writing the explicit joins is not necessary. Again, joins are really cool, and power users will be able to use them when needed. E.g. the occasional NATURAL FULL OUTER JOIN can still be done! But let’s admit it, 80% of all joins are boring, and could be replaced with the above syntax sugar.

Suggestion for Java

Of course, this suggestion will not be perfect, because it doesn’t deal with the gazillion edge cases of introducing such a significant feature to an old language. But again, if we allow ourselves to focus on the big picture, wouldn’t it be nice if we could:

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

class Book {
  String title;
}

And then:

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();

Ignore the usage of arrays, it could just as well be a List, Stream, Iterable, whatever data structure or syntax that allows to get from a 1 arity to an N arity. Or to get a set of author’s books:

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

Could it mean anything other than that:

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

Why do we have to keep spelling these things out? They’re not business logic, they’re meaningless, boring, infrastructure. While yes, there are surely many edge cases (and we could live with the occasional compiler errors, if the compiler can’t figure out how to get from A to B), there are also many “very obvious” cases, where the cerimonial mapping logic (imperative or functional, doesn’t matter) is just completely obvious and boring. But it gets in the way of writing and reading, and despite the fact that it seems obvious in many cases, it is still error prone! I think it’s time to revisit the ideas behind APL, where everything is an array, and by consequence, operations on arity 1 types can be applied to arity N types just the same, because the distinction is often not very useful.

Bonus: Null

While difficult to imagine retrofitting a language like Java with this, a new language could do away with nulls forever, because the arity 0-1 is just a special case of the arity N: An empty array. Looking forward to your thoughts.

6 thoughts on “Could we Have a Language That Hides Collections From Us?

  1. Wow, Lucas, this may be the 1st time I’ve ever been in 100% agreement with you! :-) And you are absolutely right, APL still has things to teach us.

    It would be interesting to attempt to write a copy-iterate method that, via tons of reflection, could actually implement some of what you’re asking for. It would be a horrible approach, of course, but I think it might make a good proof-of-concept. Just off the top of my head, it seems like it would be possible to squeeze a new syntax into Java that would allow for this. But I could well be wrong.

    1. I mean, that’s a drastic increase from the otherwise 99% agreement with me, right? ;-)

      I’m sure it’s very very difficult to get this right in Java, with all the edge cases, and with all the forward compatibility in mind, for future releases. Though, I’m not sure what your copy-iterate / reflective approach would look like. Do you have an example?

  2. I’m pretty sure in Scala you can do something like this. Essentially, what you need is to define an operator that does “.map {}”, add some implicits to handle 1->N and 0->N convertions, flatmaps.
    Should be easy enought in theory, but a brainfuck in practice. For example
    “int[] firstNameLengths = authors.firstName.length()” initally I thought it should have been length of authors.firstName, but it can’t be dealt via different operators, I guess

  3. The person debugging the code needs to know the type of the variable library to know whether library.title is getting the property title from a library object, or synatctic sugar over running a stream. Seems to be too big of a difference to totally hide from the developer. It won’t aid understanding. I would use a different operator than “,”, just to disambiguate to the reader. What about “:” which hints at “,” but in a row? So that the later case would be String titles = library:title;.

    1. In my “perfect world” (which is APL), this distinction is not necessary, because the arity 0-1 is an artificial special case of the generalised arity 0-N. Yes, in an existing language like Java, a Groovy style spread operator would probably be more pragmatic to avoid confusion. But in a new language that embraces APL’s paradigms, this would not be necessary.

Leave a Reply to lukasederCancel reply