My nullity concept can handle higher kinded types quite well. No can
of worms. I'll show you with some examples:

Taking the standard example from Scala for higher kinded types (see
http://www.cs.kuleuven.be/~adriaan/files/higher.pdf): An interface
that declares that a certain container type is capable of running the
map function. This is Iterable in scala, but as we already have a
completely different Iterable in java, let's call our interface
'Mappable' to avoid confusion. This thing technically has two separate
type parameters: Not just the parameter of the type it can iterate
over (the String in ArrayList<String>), but also the type of container
(the ArrayList in ArrayList<String>).

I'm going to use assume a BGGA-like shorthand syntax is allowed for
declaring anonymous inner classes for a moment. Closures would be
better but I don't want to get too far away from real life java
syntax, to make sure the example is easy to understand.

This is the no-null-typing supported version.
T is the type of the elements stored in the container (e.g. String),
and C is the container type itself, e.g. ArrayList


public interface Mapper<F, T> {
    public T map(F from);
}

public interface Mappable<T, C> {
    public <U> C<U> map(Mapper<T, U> mappingFunction);
}

public class ArrayList<T> implements Mappable<T, ArrayList> {
    public <U> ArrayList<U> map(function[T => U] mappingFunction) {
        ArrayList<U> out = new ArrayList<U>();
        for ( T t : this ) out.add(mappingFunction.apply(t));
        return out;
    }
}

public void usage() {
    List<String> list = new ArrayList<String>();
    list.add("1234");
    list.add("10");
    Mapper<String, Integer> string2int = { String x: return x ==
null ? 0 : Integer.parseInt(x); };
    List<Integer> ints = list.map(string2int);
}

Let's new rewrite this using my system to proper null-capable code. We
have a boatload of parameters here, so it's quite a complex case:

1. 'T' (the type of the container) could allow nulls, it could deny
nulls, or it could be declared to be capable of holding either kind,
which means you need to be defensive (never add nulls, but when
reading, check for them).
2. The container itself might be allowed to be null, or not, or
unknown.
3. The mapper might declare itself to never produce nulls, which
should certainly be reflected in the C<U> that is returned from the
map method.

In the example we'll use the 'non-null is the default' version of the
syntax, which means:

nothing = non-null
question mark = definitely allows null
star = either way is acceptable, but as a result we need to be defense
(e.g. check for null when reading, never put null in when writing).

first the interfaces:

public interface Mapper<F*, T*> {
    public T map(F from);
}

public interface Mappable<T*, C> {
    public <U*> C<U> map(Mapper<T, U> mappingFunction);
}

Almost the same as before, but we have added some *s to signal that
we're okay with the contents of our container being null, or with our
mapper returning null. We're not okay with the returned container type
being legally allowed to hold null; our Mappable interface demands
that we return something useful.

Note how U's nullity state is carried over from the mapper to the
returned container in the map function. If your mapper function says
that it will map to something that definitely isn't null, then you'll
get a container out that also says that its elements can never contain
null. Just as we want.

Now the arraylist code:

public class ArrayList<T*> implements Mappable<T, ArrayList> {
    public <U*> ArrayList<U> map(Mapper<T, U> mappingFunction) {
        ArrayList<U> out = new ArrayList<U>();
        for ( T t : this ) out.add(mappingFunction.apply(t));
        return out;
    }
}

Exactly the same, other than 2 stars. Some important points though:
This function MUST return something; if at any point it had 'return
null;' anywhere in its code, that would have been a compile time
error. The burden of assessing nullity status of the t's is not
relevant to the arraylist, that's a worry for the mapping function.
The fact that this method doesn't need to have any syntax to deal with
it is thus a very important property: null troubles don't usually
infect everything, just the point where it actually matters. It seems
like the mappingFunction might return null (after all, U says it may
or may not allow this), and thus that the out.add() call might add
null, *BUT*, the generics type of ArrayList 'out' is the exact same
generics parameter as mappingFunction's return type. The compiler can
also see this, and thus, it's allowed.


public void usage() {
    Mapper<String*, Integer> string2int = { String* x: return x ==
null ? 0 : Integer.parseInt(x); };

    List<String?> list = new ArrayList<String?>();
    list.add("1234");
    list.add(null);
    list.add("-5");
    List<Integer> ints = list.map(string2int);

    List<String> nonNullList = new ArrayList<String>();
    nonNullList.add("5678");
    nonNullList.add("-10");
    List<Integer> ints 2 = nonNullList.map(string2int);
}

In the above example, the string2int mapper is capable of handling
nulls, but it does not rely on the input argument being null-capable
(not even relevant as it only reads, hence the *). This is a good
thing, because if it hadn't declared itself to be null-capable, it
would not be a legal argument to list.map (but it would have been just
fine for nonNullList.map, of course). The null check inside is not
just to return the right thing, but it is in fact mandatory, if we
assume for a moment that the core libraries have been updated and
Integer.parseInt only accepts a String (and not a String?). Just
returning Integer.parseInt(x) would have been a compile time error
(the error would be: "You forgot to null-check x.").

Also note that our null-capable function is also legal to pass into
our nonNull list, but only because T is read-restricted. If we had
used a ? there, that wouldn't have been legal. Inferring that in this
particular case, '?' is just as good as '*' isn't hard but that kind
of inference is not traditionally something java has ever done, so I
haven't done it here either. For scala it would make perfect sense to
just infer this and make * and ? syntactically equivalent for that
method. (Things would have been different if it was a List<T*> vs. a
List<T?>, where the closure calls list.add(null). Or if the T type was
'exported' by e.g. creating a new List<T> in the code. Then the
inference engine would obviously no longer infer that ? was just as
good as *).



>  Is there a way we can specify nullibility at definition site
> instead of usage site?

Just like generics, you have to define in both places.

>  Or is that a bad idea?  How does this play
> with type inference?

Just like generics, you can infer quite a lot if you so choose. You
can also avoid going overboard. Heck, you can go so far as to infer
that in our mappingFunction above, the code does a null check and only
reads, so the compiler can infer that String* is the more useful type
there instead of either String or String?.

>
> I'm not literally looking for the answers to all those questions.  I'm
> just saying that they all need thought, and design, and
> experimentation.
>

Sure. Which language change doesn't? What's your point, other than
this 'duh' moment?

> If it's as easy to do as you think it is then the Scala team will be
> happy to look at a patch.

That bug report I filed hasn't seen any action for months. Not a word.
Therefore, the onus of proving that you're willing to accept community
patches from people not intricately involved in the scala community is
now on them and no longer on me. I also have no real interest in doing
this because I have abandoned scala for the time being. I'd rather
write up a JSR.


--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups "The 
Java Posse" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to 
[email protected]
For more options, visit this group at 
http://groups.google.com/group/javaposse?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to