Hacker News new | ask | show | jobs
by justinpombrio 928 days ago
Well on the other end of the extreme we had Java, which had an awful lot of this:

    HashMap<String, List<int>> hashmap = new HashMap<String, List<int>>();
Latest Java lets you omit the left type annotation (I hear, haven't tried it):

    var hashmap = new HashMap<String, List<int>>();
And in many languages the type parameters on the right can also be inferred --- so long as later code determines them --- so you get something like:

    var hashmap = new HashMap();
Hopefully we can all agree that the first option is needlessly verbose. There's more contention between the last two, I think.

My preference would be to do some type inference, but maintain the property that you can tell the type of every expression without looking outside of it (except perhaps for an immediate enclosing function call). This requires, for example:

- The third option isn't allowed, you need to write the second option instead.

- You must annotate function argument types.

- In Rust, you couldn't write `.collect()`, you'd instead write `.collect::<Vec<_>>()`.

- The `x := 10` example is actually somewhat ambiguous. If the language fixes the type of `10` then `x := 10` is legal. If it's an unspecified type (as is typically the case), you'd have to write the type down.

4 comments

> The `x := 10` example is actually somewhat ambiguous. If the language fixes the type of `10` then `x := 10` is legal. If it's an unspecified type (as is typically the case), you'd have to write the type down.

For that case I like a type signifier as part of the number literal expression, like this: `x := 10f32` or `x := 10i32`.

I have often used a Java utility class of static inferring generic constructors:

    public static <E> ArrayList<E> newArrayList() { return new ArrayList<E>(); }
    public static <K,T> HashMap<K,T> newHashMap() { return new HashMap<K,T>(); }
So code can look simpler, but just as clear:

    import static GenericConstructors.*;

    ...

    ArrayList<String> names = newArrayList();
    HashMap<String, List<int>> hashmap = newHashMap()
The "x := 10" example is one reason it feels safer to me to declare the variable type, and infer the constructor types, than the other way around.

--

What would resolve this whole issue is standardized editor/IDE visualization support for showing all inferred types, just one toggle button/key away.

Inferred types simplify writing code. But when reading code, why should we have to mentally emulate the language's inference algorithm? It is, by definition, supposed to be automating that for us.

Inferring diamond types has been built into the language for _decades_.

Your static utility functions save only 2 characters, but will add massive confusion for other developers.

  List<String> names = new ArrayList<>();
or even better just use var:

  var names = new ArrayList<String>();

Those static functions are neither simpler nor cleaner. Don’t do this.
> Well on the other end of the extreme we had Java, which had an awful lot of this: HashMap<String, List<int>> hashmap = new HashMap<String, List<int>>();

This is untrue. Inferring diamond types has been built into the language for over a decade.

    Map<String, List<Integer>> map = new HashMap<>();
I don't think you're actually disagreeing, since justinpombrio said Java had a lot of type parameter verbosity, which implies it no longer does.
Would you be willing to use a language that looked verbose in the text files, but you had an IDE that hid the verbose parts from you?

Take your Java example. If the code in the .java file looked like

    HashMap<String, List<int>> hashmap = new HashMap<String, List<int>>();
but IntelliJ showed it to you like

    hashmap = new HashMap();