Skip to main content

Completely wild starter: type safe subtypes of primitives using erasure

8 replies [Last post]
brucechapman
Offline
Joined: 2004-03-18
Points: 0

Disclaimer: this is more of a brainstorming contribution rather than a well thought out proposal.

It is inspired by threads on complex numbers and such like, and by the erasure mechanism of generics.

Starter:

Add support in the language for type safe sub types of primitives, these erase at compile time (after type checking) to their primitive supertype.

Example Definition

Note: this is not about Complex numbers, I am just using that as one possible use case. (the only one I can think of without actually thinking about it)

<br />
/*<br />
file: Complex.java<br />
dunno really, it needs to go somewhere, buts its not a<br />
class or interface, but hey we've just added enum and<br />
@interface as new top level elements.<br />
*/<br />
primitive Complex extends int;<br />

Example Explanation
Complex becomes a type usable in method and constructor arguments and return types, and as field and local variable type. But not as a type parameter bounds because its a subtype of a primitive. We sort of do an erasure type thing after type checking to erase it back to an int.

If a method takes a Complex, you must pass it a Complex, not an int.

So how do you get one, its not a class, so you don't construct one. I guess you would cast to it.

<br />
Complex = (Complex)5;<br />

Why?

Well thats a good question. I guess I see this as maybe an enabling technology. Can others see where this would be useful?

The use case I am thinking of (which by itself is completely insufficient to justify this feature) is as follows.

use Case 1 of 1

People are talking about putting immutable objects on the stack rather than the heap. They want complex numbers etc on the stack. One approach might be to give people the tools to make their own stack, where they manage their own objects, similar in a way to the RT for java stuff.

Imagine a complex number class. You construct an instance of it which is really a fixed size array of complex numbers (actually 2 arrays, one for each component). Constructor arg specifies the size. All the complex math operator methods take and return a Complex which is a subtype of int, and is the index where that Complex number is in the internal arrays. You can then do a whole heap of complex number operations, BUT all the complex numbers you use are actually (in the JVM) ints, and can therefore be on the stack if they are local variables. So Complex looks something like this

<br />
class ComplexLib {<br />
    private float real[], imaginary[];<br />
    int size;<br />
    ComplexLib(int size) {<br />
        real = new float[size];<br />
        imaginary = new float[size];<br />
        this.size=0;<br />
    }</p>
<p>    Complex create(float r, float i) {<br />
        size++;<br />
        real[size]=r;<br />
        imaginary[size]=i;<br />
        return (Complex)size;<br />
    }</p>
<p>    Complex add(Complex a, Complex c) {<br />
        /* this is probably wrong - its years since I<br />
        forgot about complex arithmetic */<br />
        return create(real[a]+real[c],<br />
            imaginary[a] + imaginary[c]);<br />
    }</p>
<p>    void assign(Complex lhs, Complex rhs) {<br />
        real[lhs]=real[rhs];<br />
        imaginary[lhs]=imaginary[rhs];<br />
    }<br />
}<br />
<br />
private Complex doSomething(ComplexLib env) {<br />
    Complex a,b,c;<br />
    a=env.create(0,1);<br />
    b=env.create(1,0);<br />
    c=env.define() ; allocates a new one, value is 0,0<br />
    env.assign(c,env.add(a,b));<br />
    return c;<br />
}<br />

Yeah, Yeah, I know, heaps of details to get right. Enter and Exit stack frames; Should you have to cast a Complex to an int before using as an array index? (probably not since Complex is a subtype of int, just not the other way around. but that would mean you could << a complex and & two of them - :( and accidentally add them : probably means you must explicitly up cast as well as down cast); bla bla bla.

Maybe this just boils down to type safe array indices?

Anyway that's it.

Yes I know the complex use case would be enhanced by the anti operator overloading operator definition thingy here http://forums.java.net/jive/thread.jspa?threadID=132&tstart=0

Remember, anything this can do, can be done today, just use the primitive, all this would add is a level of type safety at compile time, just like generics did compared with casting from Object.

Time is limited so the discussion (if any) is probably more valuable if it looks at use cases where this could be useful, rather than arguments of syntax and rules. That can come later IFF we can find hundreds of places where this makes life significantly easier or safer.

So have you got a problem where this suddenly make a better solution feasible?

Flame throwers on, aim, FIRE

Reply viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
5er_levart
Offline
Joined: 2003-10-28
Points: 0

Well, your use case is really not a representative one.

What you're trying to do is disguise an int into an opaque handle which gives you access to the "hidden" data held somewhere and let compiler type check that handle.

All that and a lot more (GC, inheritance, ...) is already there when you use a reference type. A reference type variable is actually a handle (pointer) to some data. And it is also type checked.

Your use case could be rewritten to this:

[code]
public class Complex
{
float real;
float imaginary;

Complex(float r, float i) {
real=r;
imaginary=i;
}

Complex add(Complex a, Complex c) {
return new Complex(a.real+c.real,
a.imaginary + c.imaginary);
}

void assign(Complex lhs, Complex rhs) {
lhs.real=rhs.real;
lhs.imaginary=rhs.imaginary;
}
}
[/code]

... notice the reduction of code?
Can you tell me what you have achieved with you use case that is not achieved with the modified example above?

brucechapman
Offline
Joined: 2004-03-18
Points: 0

Sure,

In a sentence it achieves much better memory usage, and reduces GC overheads.

One of the complaints (see other topics in this forum), is a performance issue when using heavyweight objects for small immutable things. Some people were asking for escape analysis, others want immutable classes stored on the stack.

If java contained everything that everyone wanted, it would be monstrous. What needs to be done, is to acknowledge the problems, then see if there is some form of base enabling technology that allows all those people to solve their own problems. One meta solution enabler, for a whole range of problems. Good languages are like that, they are a toolkit for solving problems, not a collection of solutions.

While I acknowledge the sorts of problems the complex math people (for example) are talking about, the JVM changes and such like needed for the proposed solutions just seems untenable.

SO.. Is there a way to achieve something pretty much equivalent, but coming at the problem from a different angle? Can they have their own stack and manage it in the API, in such a way as to avoid massive tiny object allocation and deallocation, and the overhead of having an object on the heap (which about doubles the space required to store a Complex number).

It seems to me that having these type safe subtypes of primitives, might just be building block to enable such solutions.

Sure, if you aren't worried about performance and memory usage (and that is probably true for a large number of applications), the Complex Class is fine, but there are applications where those overheads are unacceptable. IF java can provide a mechanism to enable library writers to optimize heavily in these cases, then that is a good thing, because it has all sorts of uses. If java just has complex numbers built in, that is no use to the rest of us.

If the compiler stops you passing a
[code]primitive kgMass extends float[/code]
to a method expecting a
[code]primitive poundMass extends float[/code]

Then that is way better than if they are all just floats.

At least one space mission may not have failed if their language had made (and enforced) this type of distinction.

mthornton
Offline
Joined: 2003-06-10
Points: 0

I suspect that the performance costs of the indirection would exceed the gains in reduced GC. Both escape analysis and (lightweight) immutable classes would also benefit the wider community beyond those using Complex.

brucechapman
Offline
Joined: 2004-03-18
Points: 0

> Both escape analysis and (lightweight) immutable classes would also benefit the wider
> community beyond those using Complex.

Exactly,

But this is not about Complex, it is about subtyping primitives.

But we are very unlikely to get escape analysis.

We may get lightweight immutable objects some time (it has been talked about for years).

So the question is, is there a mechanism that allows us to build our own when we need it? I think this is possible but any solution is fragile because an arbitrary primitive might be used where only a subset with special meaning are valid. Typedefs/subtypes of primitives, might be sufficient to make these sorts of solution feasible, for a whole range of applications.

> I suspect that the performance costs of the indirection
> would exceed the gains in reduced GC.

There is indirection involved with objects as well. Footprint is certainly reduced.

murphee
Offline
Joined: 2003-06-10
Points: 0

So... basically, what you want is "typedef"?

brucechapman
Offline
Joined: 2004-03-18
Points: 0

Well yes, I guess it is a very limited form of typedef. That is one way to look at it.

monika_krug
Offline
Joined: 2004-10-14
Points: 0

Why should complex be a subtype of int? As a subtype of double they would be more logical and there would not be a problem with array indeces.

Monika.

brucechapman
Offline
Joined: 2004-03-18
Points: 0

No,

In the example Complex is actually like a reference to a complex, an index into an array. But becuase of that there is no GC, till the whole ComplexLib instance was GCed, the representations (references) of the complex numbers, are then completely on the stack, not the heap.

It was just an example, Either of the 64 bit primitives could be used to hold 2 floats to represent a complex as well.