Type inferencing

T

Timbo

Hi everyone,

A question about type inferencing in Sun's compiler: I have the
following factory method for creating an empty list:

public static <E> List<E> list()
{
return new java.util.ArrayList<E>();
}

Now, consider the following method that takes a list of String:

private void useStringList(List<String> stringList)
{ ... }

I can invoke "useStringList" on an empty list as follows:

List<String> strList = Factory.list();
useStringList(strList);

The compiler will infer from "List<String> strList" that the
return type of "Factory.list()" is "List<String>" in this
instance. However, take the following snippet:

useStringList(Factory.list());

The compiler complains that "useStringList" cannot be applied to
"List<Object>". Why is it that it can infer the type in the former
example, but not the latter? I am aware that I can write:

useStringList(Factory.<String>list());

but this is a bit comvoluted when compared to just
"Factory.list()", and there is certainly enough information to
infer the type.

Thanks,
Tim
 
O

Oliver Wong

Timbo said:
Hi everyone,

A question about type inferencing in Sun's compiler: I have the following
factory method for creating an empty list:

public static <E> List<E> list()
{
return new java.util.ArrayList<E>();
}

Now, consider the following method that takes a list of String:

private void useStringList(List<String> stringList)
{ ... }

I can invoke "useStringList" on an empty list as follows:

List<String> strList = Factory.list();
useStringList(strList);

The compiler will infer from "List<String> strList" that the return type
of "Factory.list()" is "List<String>" in this instance. However, take the
following snippet:

useStringList(Factory.list());

The compiler complains that "useStringList" cannot be applied to
"List<Object>". Why is it that it can infer the type in the former
example, but not the latter? I am aware that I can write:

useStringList(Factory.<String>list());

but this is a bit comvoluted when compared to just "Factory.list()", and
there is certainly enough information to infer the type.

Because of method overloading, you must know the types of your
parameters before you can choose which method to call. In the line
"useStringList(Factory.list());" the compiler doesn't know yet which
useStringList is going to be called until it knows the type of
Factory.list(). So it first evaluates the type of Factory.list(). Since
there is no context information saying otherwise, it determines
Factor.list() to return an object of type List<Object>. Now it tries to find
a method matching the signature useStringList(List<Object>) and finds none,
hence the error.

- Oliver
 
R

Roedy Green

Because of method overloading, you must know the types of your
parameters before you can choose which method to call. In the line
"useStringList(Factory.list());" the compiler doesn't know yet which
useStringList is going to be called until it knows the type of
Factory.list(). So it first evaluates the type of Factory.list(). Since
there is no context information saying otherwise, it determines
Factor.list() to return an object of type List<Object>. Now it tries to find
a method matching the signature useStringList(List<Object>) and finds none,
hence the error.

does not the parameter overload matching proceed totally ignoring
genericity?
 
J

John C. Bollinger

Roedy said:
does not the parameter overload matching proceed totally ignoring
genericity?

Pretty much yes. As I understand it, method signatures are not
differentiated one from another by genericity, which means among other
things that you cannot have overloaded versions of a method that differ
only in their type parameters. This is an implication of type erasure.
The compiler checks type parameter compatibility after determining the
appropriate method to invoke (without consideration of type parameters).
Oliver's [elided] explanation of why type is not inferred for method
arguments may nevertheless be about right, but it seems to me that it
calls out an issue with the compiler architecture rather than anything
inherent in the type system. I wonder whether it's really just a
shortcut to limit the intelligence that the compiler needs to exercise.
 
T

Timbo

Oliver said:
Because of method overloading, you must know the types of your
parameters before you can choose which method to call. In the line
"useStringList(Factory.list());" the compiler doesn't know yet which
useStringList is going to be called until it knows the type of
Factory.list(). So it first evaluates the type of Factory.list(). Since
there is no context information saying otherwise, it determines
Factor.list() to return an object of type List<Object>. Now it tries to find
a method matching the signature useStringList(List<Object>) and finds none,
hence the error.
This is the best theory I've heard so far :) But (as stated by
Roedy in his reply) generics are not used in overloading, so it
should match the "List" part and then infer that the type
parameter is "String".

Perhaps Sun are planning that in the future type parameters are
not erased from the bytecode, thus making my above example
erroneous to support compatability between versions?
 
T

Timbo

John said:
I wonder whether it's really just a
shortcut to limit the intelligence that the compiler needs to exercise.
Perhaps, but I'm guessing that this would not be a major change.
As far as I can tell, type parameters would be substituted with
unground type variables, and then the two types unified to infer
the instantiation of the parameter (or something similar). The
infrastructure to do this would already be in place.
 
C

Chris Uppal

John said:
Oliver's [elided] explanation of why type is not inferred for method
arguments may nevertheless be about right, but it seems to me that it
calls out an issue with the compiler architecture rather than anything
inherent in the type system. I wonder whether it's really just a
shortcut to limit the intelligence that the compiler needs to exercise.

Another place where it seems that the compiler /should/ be able to infer types,
but does not is in conditional code. E.g. (from memory)

List<String> stringList = null;
if (...)
stringList = Factory.list();
else
stringList = ASimilarFactory.list();

is OK, but:

List<String> stringList = (...)
? Factory.list()
: ASimilarFactory.list();

is not.

Personally, I'm going off this type inference fast[*] (and I wasn't too keen on
it in the first place), but if we are going to have it, then at least it should
be done properly...

-- chris

([*] It seems obfuscatory, and is also IMO contrary to the spririt of a
language in which
byte b = ...;
byte lowNybble = b & 0x0F;
is not allowed.
)
 
R

Robert Klemme

Chris said:
John said:
Oliver's [elided] explanation of why type is not inferred for
method arguments may nevertheless be about right, but it seems to me
that it calls out an issue with the compiler architecture rather
than anything inherent in the type system. I wonder whether it's
really just a shortcut to limit the intelligence that the compiler
needs to exercise.

Another place where it seems that the compiler /should/ be able to
infer types, but does not is in conditional code. E.g. (from memory)

List<String> stringList = null;
if (...)
stringList = Factory.list();
else
stringList = ASimilarFactory.list();

is OK, but:

List<String> stringList = (...)
? Factory.list()
: ASimilarFactory.list();

is not.

Note that this happens also with non generics if you have compatible but
differing types. For example

InputStream in = isFile()
? new FileInputStream("f")
: new ByteInputStream(buffer);

The only solution here is to explicitely cast both new streams to
InputStream. Awful.
Personally, I'm going off this type inference fast[*] (and I wasn't
too keen on it in the first place), but if we are going to have it,
then at least it should be done properly...

-- chris

([*] It seems obfuscatory, and is also IMO contrary to the spririt of
a language in which
byte b = ...;
byte lowNybble = b & 0x0F;
is not allowed.
)

This has bugged me ever since, too...

Kind regards

robert
 
T

Thomas Hawtin

Robert said:
Note that this happens also with non generics if you have compatible but
differing types. For example

You mean if one type is not a supertype of the other.
InputStream in = isFile()
? new FileInputStream("f")
: new ByteInputStream(buffer);

That should work in 5.0.
The only solution here is to explicitely cast both new streams to
InputStream. Awful.

You don't have to do it explicitly. You can assign one of the references
to a local variable of the desired type.

Tom Hawtin
 
R

Roedy Green

Personally, I'm going off this type inference fast[*] (and I wasn't too keen on
it in the first place), but if we are going to have it, then at least it should
be done properly...

As I see it, why did you put do genericity in the first place?

You wanted type SAFETY.

Sun has introduced a pseudorandom variable. If you leave out the type
information, it might mean let inference handle it, or it might mean
turn off type checking.

So you are back where you started, without a guarantee that types are
consistent.

So I would say, unless you are very sure inferencing can be trusted,
leave the type markers in.

On the other hand, others reading your code may not be so sure. It is
nice for general knowledge to see the types.

Yet on the other hand putting types in a million places makes your
code harder to change. In a sensible language, you only specify the
type of a variable in one place -- where it is declared.

If the guys who designed Java's generic type system were locksmiths,
padlocks locks would weigh 10 pounds, would have 40 dials, and could
be picked with a bobby pin, and you could not tell by looking if they
were locked.
 
R

Roedy Green

InputStream in = isFile()
? new FileInputStream("f")
: new ByteInputStream(buffer);

The only solution here is to explicitely cast both new streams to
InputStream. Awful.

Of course to fix this, Sun would have to solve it for all possible
pathological cases as well, not just the ones that are blindingly
obvious how they should be handled.

Try thing one :

Object o = condition ? 1 : "2";

What should appear Integer(1) or "1"?

It gets hairier when ? is used in an expression where there is no
clear target type:

Then there is the matter of whether the compiler will ever or never
evaluate both conditions.

In Forth, the ordinary IF also doubles as the ? operator. However,
there are no automatic conversions, so the problems you run into in
Java with ? don't come up.
 
R

Roedy Green

A question about type inferencing in Sun's compiler:

So when is Java smart enough to figure out the types on its own via
inferring? It sometimes feels as if it depends on the phases of the
moon. Inferring has even the cleverest programmers scratching their
heads. Nobody can figure out for certain why sometimes the compiler
can infer the generic types and sometimes it cannot. So what can you
do?

If you have Eclipse, you can rapidly experiment, removing the explicit
types one by one and putting them back if Eclipse complains. If you
don't have Eclipse, you can do the same thing more slowly with a
compile cycle.

The problem with removing type tags is if you remove one too many
tags, you are flying without a net. You have effectively turned off
type checking. It is difficult to tell the difference between allowing
inference to check types and turning off type checking altogether.
This is a serious flaw in the design of Java generics.

Generic type tags are useful documentation to let others know what
your code is up to. That is an argument for leaving them in.

If you burn the generic types into your code in 100 places, it will be
hard to modify later if you change that type. Ideally you should
specify the type of a variable in only one place — where it is
declared. That is an argument for removing the type tags wherever you
can.
 
O

Oliver Wong

Roedy Green said:
If you have Eclipse, you can rapidly experiment, removing the explicit
types one by one and putting them back if Eclipse complains. If you
don't have Eclipse, you can do the same thing more slowly with a
compile cycle.

While this might work for type inference experimentation (I haven't
tried), this is not foolproof in general. The Eclipse compiler and JavaC
compiler differ slightly (I've written a program that had no red squigglies
in Eclipse, but which had a compile-time error according to JavaC), and
obviously if they disagree, JavaC has authority.

- Oliver
 
S

Stefan Ram

Robert Klemme said:
InputStream in = isFile()
? new FileInputStream("f")
: new ByteInputStream(buffer);
The only solution here is to explicitely cast both new streams to
InputStream. Awful.

IIRC, it was sufficient to cast one of them to the common
supertype. Nowadays the JLS3 says:

"The type of the conditional expression is the result
of applying capture conversion (§5.1.10) to lub(T1, T2)
(§15.12.2.7)." -- JLS3, 15.25

and Java 1.5 actually compiles this:

try
{ byte[] buf = new byte[ 10 ];
java.io.InputStream in = java.lang.Math.random() < 0.5
? new java.io.FileInputStream( "f" )
: new java.io.ByteArrayInputStream( buf ); }
catch( final Exception e ){}
 
T

Thomas Hawtin

Roedy said:
Of course to fix this, Sun would have to solve it for all possible
pathological cases as well, not just the ones that are blindingly
obvious how they should be handled.

Try thing one :

Object o = condition ? 1 : "2";

What should appear Integer(1) or "1"?

Integer.valueOf(1). According to javac, the type of condition ? 1 : "2"
is Object&Serializable&Comparable<? extends
Object&Serializable&Comparable<?>>. I guess in some sense it should be
something like <T extends Object&Serializable&Comparable<T>> T, not that
it matters much in this case. Perhaps they should have gone with | or ^
to go with &.

Try this:

class Ternary {
Class x = true ? 1 : "2";
}

Before 1.5 intersection types were only available in bytecode, on the stack.
Then there is the matter of whether the compiler will ever or never
evaluate both conditions.

Compile time constants are irrelevant as far as types are concerned.

Tom Hawtin
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
474,431
Messages
2,571,679
Members
48,796
Latest member
Greg L.

Latest Threads

Top