Proposal: Simplified syntax for dealing with parameterized types (correction to ALTERNATIVES section)

james lowden jl0235 at yahoo.com
Mon Mar 23 13:22:49 PDT 2009


Proposal: Simplified syntax for declaring with parameterized types.

AUTHOR(S):

J. Lowden

VERSION:

1.0    Initial version.


OVERVIEW

FEATURE SUMMARY:

Declaring and instantiating parameterized types can be verbose (resulting in expressions like "HashMap <String, String>" being repeated throughtout one's code) and, due to the erasure-based implementation of generics in Java, not necessarily type-safe.  The code snippet below illustrates both:


  HashMap <String, String> a = new HashMap <String, String> ();
  HashMap b = new HashMap ();
  b.put ("foo", new JFrame ());
  a = b; // this is semantically wrong, but compiles


In this case, the parameterization language ("<String, String>") is repeated twice, which quickly becomes annoying if one is using a large number of String-to-String hashes.  More dangerously, despite "a" having been declared as a HashMap <String, String>, it is possible to use it as a reference to a non-parameterized HashMap which may contain objects of unexpected and semantically incorrect types.  One way around this problem is by subclassing the parameterized type, as in:


  public class StringyMap extends HashMap <String, String> {
  }


The above example then becomes:


  StringyMap a = new StringyMap ();
  HashMap b = new HashMap ();
  b.put ("foo", new JFrame ());
  a = b; // this now causes a compiler error


This both removes the repetition and causes the type-unsafe assignments statement to generate a compiler error.  However, since simply extending a class causes the loss of all but the default constructor, in order to make StringyMap as useful as HashMap <String, String>, we really need to do this:


  public class StringyMap extends HashMap <String, String> {

    public StringyMap () {
    }

    public StringyMap (int initialCapacity) {
      super (initialCapacity);
    }

    public StringyMap (int initialCapacity, float loadFactor) {
      super (initialCapacity, loadFactor);
    }

    public StringyMap (Map <String, String> m) {
      super (m);
    }

  }


This is an annoying heap of boilerplate code, especially seeing that verbosity was one of the issues we were trying to address.  Therefore, the new syntax being proposed is something along the lines of:

  public class X = Y <list of type parameters>;

which would be exactly equivalent to:

  public class X extends Y <list of type parameters> {

    // constructors

 }

with constructors matching the signatures of the superclass constructors automatically generated that simply pass all the parameters along to the superclass constructor.

This could also be done with interfaces.  Since interfaces do not support constructors, the mapping is simpler, as we simply leave the automatic constructor creation out:

  public interface X = Y <list of type parameters>;

is equivalent to:

  public interface X extends Y <list of type parameters> {}


Java currently requires each top-level public class or interface to be declared in its own source file.  When working with multiple parameterized types, this could result in a large number of one-line source files that may be inconvenient to deal with.  Therefore, I also propose the creation of an optional special source file in each package that can be used to contain multiple such type definitions.  This file would be named something like "param-types.java" (the specific name doesn't matter as long as it is A) standardized and B) not a legal Java identifier, so we avoid collisions), and all public classes or interfaces defined in it would be compiled as though they were top-level class files in the given package.  The "param-types.java" file would be restricted to containing classes/interfaces defined using the new syntax described above; this file is intended as a convenience mechanism to support the features described above, not a mechanism for
 cramming all of one's code into one hideously-long source file.

Although not a principal goal of this proposal, this also provides an easy mechanism for enforcing type safety between two types that have the same parameters but are not intended to be interconvertible.  For example:

public interface ListOfThingsToNameTheBaby = List <String>;
public interface ListOfAnnoyingInternetAcronyms = List <String>;

ListOfThingsToNameTheBaby a;
ListOfAnnoyingInternetAcronyms b;

a = b; // raises a compiler error, since these are not assignable


Note that there is potential for confusion if the two types were actually meant to be interconverible (i.e., ListOfNamesForBabies and ListOfThingsToNameTheBaby).


MAJOR ADVANTAGE:

It becomes easier and more elegant to create reusable, robust parameterized types.


MAJOR BENEFIT:

Readability of code that makes extensive use of parameterized types is improved.  Although the general erasure-related issues with type-safety of generics are not eliminated, this provides a straightforward mechanism for working with more robust parameterized types.


MAJOR DISADVANTAGE:

Programmers (especially those working on distributed projects) may inadvertantly end up defining two types meant to be "the same thing" that are not mutually assignable, as in:

public class ListOfNamesForBabies = ArrayList <String>;
public class ListOfThingsToNameTheBaby = ArrayList <String>;

ListOfNamesForBabies a;
ListOfThingsToNameTheBaby b;

a = b; // does not compile


Note that this is only a problem in cases where the types are intended to be equivalent; see the ListOfAnnoyingInternetAcronyms/ListOfThingsToNameTheBaby example above.


ALTERNATIVES:

Reification of generics combined with some kind of syntax to avoid repetition of the type parameters would be ideal, but out of the scope of "small language changes".


EXAMPLES

SIMPLE EXAMPLE:

--- creating a file named StringyMap.java in package blah

  package blah;

  // various import statements

  public class StringyMap = HashMap <String, String>;

   
--- is equivalent to what would currently result from the following:

  package blah;

  // various import statements

  public class StringyMap extends HashMap <String, String> {

    public StringyMap () {
    }

    public StringyMap (int initialCapacity) {
      super (initialCapacity);
    }

    public StringyMap (int initialCapacity, float loadFactor) {
      super (initialCapacity, loadFactor);
    }

    public StringyMap (Map <String, String> m) {
      super (m);
    }

  }

ADVANCED EXAMPLE:

--- creating a file named param-types.java in package blah

  package blah;

  // various import statements

  public class StringyMap = HashMap <String, String>;
  public interface ListOfNames = List <String>;

--- is equivalent to what would currently result from defining the StringyMap class in the previous example
--- as well as the following:

  package blah;

  // various import statements

  public interface ListOfNames extends List <String> {}


DETAILS

SPECIFICATION:

The new syntax can be handled entirely via compiler modifications.  Since this proposal consists entirely of syntactic sugar atop the existing type system and uses a syntax that currently has no meaning in Java, it should not have any affect on the meaning of any current feature of the Java Programming Language.


COMPILATION:

Compilation of this new feature would consist of de-sugaring two new types of declarations.  For interfaces, the following:

[modifier_list] interface [name] = [superinterface] <list_of_types. . .>;

is de-sugared to:

[modifier_list] interface [name] extends [superinterface] <list_of_types. . .> {}


Classes are a bit more complex:

[modifier_list] class [name] = [superclass] <list_of_types. . .>;

is de-sugared to:

[modifier_list] class [name] extends [superclass] <list_of_types. . .> {

  [constructors]

}

Where the [constructors] block consists of one "wrapped" constructor for each public or protected constructor in the superclass.  Thus, for each such constructor in the superclass:

  [public/protected] [superclass] ([constructor_args. . .]);

A "wrapper" constructor would be generated as follows:

  [public/protected] [name] ([constructor_args. . .]) {
    super ([constructor_args. . .]);
  }


Finally, when resolving dependencies, if the compiler doesn't find a class or source file for a top-level class in the expected location, the compiler would examine "param-types.java" in the package-appropriate directory to see if it is defined there.  Only classes and interfaces defined using the syntax described here are permitted in the "param-types.java"; any "traditional" class or interface definition in this file results in a compiler error.


TESTING:

This feature can be tested in the same manner as any other form of class or interface declaration.

LIBRARY SUPPORT:

None required.

REFLECTIVE APIS:

This should be automatic, as the current reflective APIs will correctly identify the class or interface.

OTHER CHANGES:

None.

MIGRATION:

Replace current use of parameterized types where feasible.


COMPATIBILITY

BREAKING CHANGES:

None. The proposed syntax currently causes compiler errors.

EXISTING PROGRAMS:

No changes.


REFERENCES

EXISTING BUGS:

None that I am aware of.

URL FOR PROTOTYPE (optional):

None thus far; work-in-progress.


--- On Mon, 3/23/09, brucechapman at paradise.net.nz <brucechapman at paradise.net.nz> wrote:

> From: brucechapman at paradise.net.nz <brucechapman at paradise.net.nz>
> Subject: Re: Proposal: Simplified syntax for dealing with parameterized types.
> To: jl0235 at yahoo.com, "james lowden" <jl0235 at yahoo.com>
> Cc: coin-dev at openjdk.java.net
> Date: Monday, March 23, 2009, 3:17 PM
> Quoting james lowden <jl0235 at yahoo.com>:
> 
> > ALTERNATIVES:
> 
> > Write strings the old way by using concatenations or
> using builders.> 
> 
> Seems somewhat out of place.


      



More information about the coin-dev mailing list