Forward declaring the identity of an object
Red IO
redio.development at gmail.com
Fri Jul 7 10:45:48 UTC 2023
I see you though of it as a compile time thing. But like you also mentioned
it would be difficult for the compiler to prove that no access occurs
before initialization severely limiting the usefulness. Also the things
with for example instanceof and synchronization are real questions as they
primary operate on identity and don't use the (garbage) data of a half
initialized object.
Like:
Foo foo = __uninitialized;
boolean b = foo instanceof Foo //true or false
syncronized(foo) { //works or not?
}
foo = new Foo();
Also in terms of value objects and primitives it doesn't really work at all
since they by definition do not have identity (in Java) so since they
cannot be referenced at all they can't ever be in this uninitialized state.
Overall cyclical REFERENCE structures don't make sense or work in datatypes
without identity.
The only big obstacle I see is that it's probably not worth the cost of
implementating. The private field or id approach both work and solve the
problem hence this is merely a convenience feature, even though the concept
might allow unforeseen possibilities not obvious right now. If other
problems are found that would be solved with it it might become viable to
implement.
Great regards
RedIODev
On Thu, Jul 6, 2023, 20:27 David Alayachew <davidalayachew at gmail.com> wrote:
>
> Hello RedIODev,
>
> Thank you for your response! And thank you for your patience, the holidays
> took up a lot of time.
>
> Let me preface this by saying that I won't have many (if any) satisfying
> answers for you. A lot of these details seem to go deep into the guts about
> how the JVM work, and while I am happy to learn if you can point me to some
> resources, the fact is, I just don't have that information in my head at
> the moment.
>
> Regardless, I'll attempt each of your bullets.
>
> > I think the biggest problems would be
> >
> > 1. How do you mark a memory location as allocated (has
> > identity aka memory address) but should still be
> > treated as null which changes later without any
> > notice? It's like a reverse WeakReference
>
> Not sure.
>
> To explain the (very nebulous/shapeless) concept I had in my head, here it
> goes.
>
> I believe that the concept of a circular reference is an inherently
> identity-based operation. Therefore, we must exclude primitives and value
> classes from the equation.
>
> Next, I am of the assumption that there exists a point in time where
> memory has been allocated, but the data has not been written to memory yet.
> My hope and prayer was that we could allow actions to occur between those
> specific 2 actions safely by allowing the compiler to "prove" that the
> allocation AND memory write will eventually happen before any dereferencing
> occurs.
>
> As a result of that, you have an address that you can pass around to other
> variables, but they are not permitted to dereference it until it is
> actually initialized with data -- aka, the memory write.
>
> Based on this ethereal concept, I don't know that the data needs to be
> marked in any way at all. We were taking 2 actions that were essentially
> contiguous, and breaking them up so that subsequent "safe" actions could
> occur in between the 2.
>
> Please let me know if I am wildly off of the mark here.
>
> > 2. How would you represent it syntactically it's neither
> > null nor a valid object it's a in between state
> > previously unknown.
>
> Truthfully, I don't really care much about what the syntactic
> representation looks like as long as it is immediately clear and obvious.
>
> The semantics, however, are a little different.
>
> I would treat whatever syntactic choice we use as a sort of "hall pass"
> for definite assignment. Basically, we have the variable uninitialized as
> usual, but then areas where we want to do an illegal forward reference, we
> put our "hall pass" in front of the variable to signify that it is "safe to
> use uninitialized".
>
> For example.
>
> class Node
> {
>
> final Node next;
>
> Node(Node next)
> {
> this.next = next; //no dereferencing has occurred for the entirety
> of this constructor, and thus, it is "safe".
> }
>
> }
>
> final Node rock, paper, scissors;
>
> rock = new Node(__uninit scissors); //the "hall pass", which triggers the
> scissors memory location to be allocated
> paper = new Node(rock);
> scissors = new Node(paper); //the scissors memory allocation is now being
> written to, completing initialization
>
> > 3. There would be many changes in the language necessary
> > to make this feature work reliably and the use beyond
> > niece things like circular references in immutable
> > datastructures are probably low.
>
> Frankly, the primary reason why I feel this feature should exist is for
> clarity's sake. We are allowing an object to represent a relationship more
> directly. If a references b and b references a, then just bootstrap them
> into each other and be done with it! Introducing indirection, mutability,
> reflection, or piles of builder code boilerplate when what is needed is a
> direct relationship sounds to me like sidestepping a problem by using
> Java's other strengths and points of flexibility. And while that strength
> and flexibility is both useful and powerful, it adds overhead that makes
> problems harder and harder to reason about. I think removing all of that is
> making coding simpler, and that is a goal worth pursuing imo.
>
> > 4. This would also bring up many questions in which cases
> > the memory behaves like a object and in which case
> > null. Like synchronization, null checks, instanceof,
> > etc.
> >
> > It's likely not worth the complexity and the cost.
>
>
> Ideally, this should not add any new runtime features, but instead, should
> be separating pre-existing features into separate actions. My hope is that
> this can be an "unsafe" operation that can be proven "safe" by the compiler.
>
> And yes, there is a limit to what the compiler can prove. For example, if
> the "constructor" in the example was actually a method on an interface,
> well we don't know what the implementing method does. Maybe the
> implementing method dereferences the variable, which then begs the question
> of what failure should look like.
>
> I am of the mindset that we should treat failure the same way that we do
> in the nonatomic concept from Valhalla and mark that as a cost developers
> can opt into to get the tradeoffs they want.
>
> As for what should literally happen during failure, well based on what I
> described, you can end up with an object containing garbage data. Of
> course, the compiler can protect you from a vast majority of these
> problems, but you opt into getting garbage data back if you use this
> feature incorrectly.
>
> Please let me know your thoughts.
>
> Thank you for your time and questions!
> David Alayachew
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/amber-dev/attachments/20230707/94dc067d/attachment.htm>
More information about the amber-dev
mailing list