Are statics broken?

Posted by bsgruenba on 20-May-2010 16:14

When I am implementing a class based on the GOF Factory pattern, a technique I often use is to provide several static methods within the class that instantiate an instance of the class. I then make the constructors private so that the class can only be instantiated using the factory method. So, for example:

class MyClass:

  method public static MyClass getNewMyClass():

    define variable clss as MyClass.
    clss = new MyClass().
    return clss.
  end.
  constructor private MyClass():
  end.
end.
Now looking at the above code, there does not seem to be a whole lot of value to this because the constructor doesn't do anything. But work with me here... I'm trying to explain some concepts as well.
One of the reasons that I use this pattern is to deal with problems associated with instantiating the class that are related to IO exceptions, or something like that. So, in this case, in order to instantiate an instance of MyClass, the caller needs to provide me with some information and I will go and retrieve the data that makes MyClass work. If something happens in the process of retrieving that data, I want to throw an exception if need be.
One of the key rules about constructors is that they should not throw exceptions. It's kind of like the one-world-one-wait-for rule, or shared variables being the devil's own spawn. So if I need to throw an exception when the class is instantiated, I wrap it in a factory method and the factory method becomes the only way to instantiate it. The factory method will then call a method on the instance of the class after the constructor has run to retrieve whatever the class needs.
So the above code now looks more like this:

class MyClass:

  method public static MyClass getNewMyClass():

    define variable clss as MyClass.
    clss = new MyClass().
    clss:retrieve().
    return clss.
  end.
  constructor private MyClass():
  end.
  method public void retrieve():

     /* do some stuff to fetch the data */

  end.
end.

and now I am only my factory method calls the retrieve method for me.
Here's where the crunch comes... No one else should be able to call retrieve so I should be able to mark it as either private or protected. The problem is that if you try and do that in OpenEdge, the code won't compile.
I'm not going to argue that there are a number of ways to work around this problem, but this is pretty basic functionality and if it's not allowed it really causes encapsulation problems. In Java or .NET I would work around it by not placing a modifier on the constructor and the method which means that the method is only accessible inside the package (for Java) or the assembly (for .NET), and then I would create a separate factory class. That is not the case with the ABL which means that I have to make these things public for this code to work.
So the question is, is this expected behavior, or just an oversight?
FTR, the following Java and C# code demonstrate the same functionality in those languages:
Java:

public class A {
    
     public static A getA() {
          A myA = new A();
          myA.retrieve();
          return myA;
     }
    
     private A() {
     }
    
    
     private void retrieve() {
          // perform some code.
     }
}

C#:

public class A
{
     private A()
     {
     }
    public static A getA()
    {
        A myA = new A();
        myA.retrieve();
        return myA;
    }
    private void retrieve()
    {
        // perform some code.
    }
}

All Replies

Posted by bsgruenba on 20-May-2010 16:25

When I wrote this initial message, I could not get at progress.com - it was down for some reason. I just managed to get at the KB and found the following bug:

  • Bug# OE00183879
  • It is never valid to access a PRIVATE or PROTECTED data member through an object reference. This is an attempt to access a data member in a separate instance of the class and PRIVATE /PROTECTED members can only be accessed from within the instance of the class/hierarchy where they are defined. The only way to access a PRIVATE or PROTECTED data members from within their class is to use either THIS-OBJECT or an unqualified reference, such as DoSomething(). However, both of those access methods are illegal inside a static method.
    The code should not compile and execute when the method is defined as PRIVATE. OpenEdge 10.2A should raise an error message.

The assertion in both the first and second sentences of this bug are invalid. It prevents proper implementation of the Factory pattern. A static method on a class is a member of the same class hierarchy and has to have the same access to sub-classes as any other member.

Posted by Thomas Mercer-Hursh on 20-May-2010 18:05

FWIW, my inclination ... independent of what is and isn't allowed, would be to do something more like:

class MyClass:

  method public static MyClass  getNewMyClass():

    define variable clss as  MyClass.
    define variable dta as whatever.
    /* do some stuff to fetch the data */
    clss = new MyClass(dta).
    return clss.
  end.

  constructor private MyClass():
  end.

end.
My thinking here is that if there is an error in fetching the data, one doesn't even try to instantiate the object and, fetching the data isn't necessarily a proper responsibility of the object anyway since the data could easily have more than one source.

Posted by bsgruenba on 21-May-2010 10:09

tamhas wrote:

FWIW, my inclination ... independent of what is and isn't allowed, would be to do something more like:

class MyClass:

  method public static MyClass  getNewMyClass():

    define variable clss as  MyClass.
    define variable dta as whatever.
    /* do some stuff to fetch the data */
    clss = new MyClass(dta).
    return clss.
  end.

  constructor private MyClass():
  end.

end.
My thinking here is that if there is an error in fetching the data, one doesn't even try to instantiate the object and, fetching the data isn't necessarily a proper responsibility of the object anyway since the data could easily have more than one source.

Fine. It still doesn't fix the problem. Without providing a lot of gory details about implementations, I have several cases like this where calling a private or protected instance method from a static method in the same class is a key part of the encapsulation.

Posted by Thomas Mercer-Hursh on 21-May-2010 11:21

My sense of the issue in your original problem is that while you have a static method, you don't actually have a static class, so, in effect, you are trying to access a private method from outside of the class.  If that is the way that ABL is seeing it, then it isn't surprising that you have a problem.

Can you say a bit more about some of the other use cases?

Posted by bsgruenba on 21-May-2010 15:38

OK... This chronically oversimplified, but here is the real-world use case, and the following is Java code that shows what I am talking about.

With the Exchange Web Services project, the Appointment object provides an interface for developers to interact with appointments. getNewAppointment retrieves a new object for people to edit, whereas getAppointment() retrieves an existing appointment.

There are appointments on the Exchange Server and there are appointments in a local database cache. The problem is that an appointment may no longer be on the Exchange Server (it was deleted per archiving policy). So when an attempt is made to retrieve an appointment from the Exchange Server and no appointment is returned, I go to the local database which will have a record of the old appointment. If one is found in the database, users should not be able to modify it, and if it is not marked as deleted in the local database, we need to make sure we set it that way in the cache (a background process will note the appointment as deleted later).

Moreover, a developer needs to be able to get an appointment and specifically say what source he wants it from.

All of this instantiation logic is something that the developer should not know about, and it is convoluted enough that the developer should explicitly tell me what he is asking for.

The code that I cannot write in OpenEdge is the code that exists in the "switch" (same as CASE) statement in the getAppointment() method that calls appt.setState() and appt.setReadOnly(), neither of which should be exposed to developers.

public class Appointment {
 
  public static Appointment getNewAppointment() {
    Appointment appt = new Appointment();
    return appt;
  }
 
  public static Appointment getAppointmentFromCache(String itemID) {
    Item item = CachedService.getDefault().getItem(itemID);
    if (item == null)
      return new Appointment(ItemSource.CACHED, ItemState.UNKNOWN, itemID);
    else
      return new Appointment(ItemSource.CACHED, item);
  }

  public static Appointment getAppointmentFromExchange(String itemID) throws IOException {
    Item item = ExchangeService.getDefault().getItem(itemID);
    if (item == null)
      return new Appointment(ItemSource.SERVER, ItemState.DELETED, itemID);
    else
      return new Appointment(ItemSource.SERVER, item);
  }

  public static Appointment getAppointment(String itemID) {
    // Get the appointment from Exchange first. We want the latest one.
    Appointment appt = null;
    try {
      appt = getAppointmentFromExchange(itemID);
    }
    catch (IOException e) {
      appt = new Appointment(ItemSource.SERVER, ItemState.UNKNOWN, itemID);
    }
    // If the appointment on the Exchange Server has been deleted, then get the cached one.
    ItemState state = appt.getState();
    switch (state) {
      case DELETED:
      case UNKNOWN:
        appt = getAppointmentFromCache(itemID);
        appt.setState(state);      // I can't do this in OpenEdge
        appt.setReadOnly(true);    // I can't do this in OpenEdge
        break;
      default:
        break;
    }
    return appt;
  }

  private ItemSource source;
  private ItemState state;
  private final Item sourceItem;
  private String itemID;
  private boolean readonly;

  private Appointment() {
    this(ItemSource.NEW, ItemState.NEW);
  }

  private Appointment(ItemSource source, ItemState state) {
    this(source, state, (Item) null);
  }

  private Appointment(ItemSource source, Item item) {
    this (source, ItemState.UNCHANGED, item);
  }

  private Appointment(ItemSource source, ItemState state, String itemID) {
    this.source = source;
    this.state = state;
    sourceItem = null;
    this.itemID = itemID;
  }

  private Appointment(ItemSource source, ItemState state, Item item) {
    this.source = source;
    this.state = state;
    sourceItem = item;
    if (sourceItem != null) {
      deserializeItem(sourceItem);
    }
  }

  public ItemSource getSource() {
    return source;
  }

  public ItemState getState() {
    return state;
  }

  private void setState(ItemState state) {
    if (!readonly)
      this.state = state;
  }

  private void setReadOnly(boolean readonly) {
    this.readonly = true;
  }

  public enum ItemSource {
    NEW,
    CACHED,
    SERVER;
  }
  public enum ItemState {
    NEW,
    UNCHANGED,
    MODIFIED,
    DELETED,
    UNKNOWN;
  }
}

Posted by Thomas Mercer-Hursh on 21-May-2010 15:58

OK, without looking at the Java and without reference to how you specifically want to implement this, my first reaction is that you have a separation of concerns issue here, in fact, a layer separation issue.  The goal is to produce a business layer object for appointment.  The issue about whether the appointment is still in Exchange, whether one has to go to the local cache, etc. is a data layer problem.  So, my inclination would be to have a data layer object which looked in Exchange, fell back to looking in the local DB, and whatever the source created a message data packet which was sent to a business logic layer factory to create the actual Appointment object.  That way, all the error and exception handling about the data source and the need to do the markup all occurs in one place and is independent of actually building the object.  The factory, which has requested the appointment, then either receives an error when the appointment can't be found or a message data packet for a valid appointment and, in the latter case, can proceed to build a valid Appointment object without concern about any of the data source error handling.

Posted by johnallengreen on 21-May-2010 16:16

I'm with you Bruce. I've done that both in Java and C#. I haven't needed it yet in ABL, but it's disappointing to learn that it's not available.

Posted by bsgruenba on 21-May-2010 17:09

*Please* can we put  a "NOT HELPFUL" button on this forum for whether the question has been answered?

Seriously, Thomas. You really want to go down the road of criticizing the architecture for a specifically code-related question? Even after I categorically stated that this is a "chronically oversimplified" example? You asked me to provide an example that demonstrated the issue. I did. At the heart of the issue is the following:

I need to instantiate the object and then make changes to properties that no one other than my class should have access to. It has to happen after instantiation. It cannot happen before. I will grant you that the cases in which this is necessary are few and far between, as are the cases for the need for global variables. Nonetheless, when these cases exist, they are extremely important and typically very convoluted to work around.

The argument could be made that we do not need temp-tables, but can you imagine the 4GL without them? And they are a relatively recent addition to the language.

One of the most fundamental features of object-orientation is encapsulation. It's probably more important than polymorphism and reusability because it enable both. When you are writing an API against which other will develop code, it is absolutely essential that you very tightly control the ways in which others can interact with that API, and you have to build very robust tests to ensure that the ways that you anticipate other will interact with it are handled. You then have to tie down that API so that people cannot interact with it in ways that you do not anticipate because, if you don't, they will do things that you don't expect and it will crash, and once its published you have to fix those things.

There are two concepts that are essential to properly encapsulating an API:

1) A package-/assembly-level modifier; and

2) The ability make calls from a static method in a class to a private member of an instance *of that class*.

If I had either one of those two, I could probably work around the other and maintain the level of encapsulation that I am looking for. Java and C# provide both. OpenEdge provides neither.

Posted by Thomas Mercer-Hursh on 21-May-2010 17:48

I'm sorry that you don't find my response helpful, but I think that there are a certain number of cases where the best answer to a question is to point out that one is asking the wrong question.  Remember the days of people bemoaning the times when a leave trigger wouldn't fire because they were trying to trap people in the field with bad data.  The right answer was Don't Do That™.

I'm not suggesting that here, necessarily, but I am suggesting that you consider two points.

One is the possibility that this is not so much a bug as it is a design feature.  We won't know that until and if a developer joins us, but I can see the point that referencing a private method from what is functionally outside the class is not something one should do.  Admittedly, this is a kind of in-between case because the method is in the class with the static method in which an instance of the class is being created.  If the method were not static, one clearly could not do this.  One could reference the method in the class doing the instantiation, but not in the one being instantiated.  Since the method is static, this is a little ambiguous since one thinks of the method belonging to the class, not the object, but still the method you are trying to reference is in the new object, so I can see why they might not think that was a valid operation.  If this is by design, then I don't think you can expect to get it fixed.

The other is that it seems to me that the example as I understand it isn't the best way to achieve the purpose.  Encapsulation is just the flip side of separation of concern, which some would consider the most key OO principle.  Here, it seems to me that there are really two separate concerns:

1) the behavior and knowledge associated with an appointment; and

2) the behavior and knowldege associated with retrieving appointment data from a persistent store.

Clearly, there are a variety of possible error conditions and behaviors associated with the latter which have nothing to do with the appoinment itself.  By the same token, the Appointment object shouldn't be persisting itself either.  This is reinforced by the principle that one shouldn't be creating an object until one knows that one has valid data necessary to create a valid object.

I know that neither of these helps you with the problem as you have it defined.  But, the first suggests that it may not be because statics are "broken", but rather that they are behaving as designed.  You may not like that design, but I think it is an understandable one.  And, the second suggests that there might be a workaround which wasn't just a klug, but rather something that follows some good design principles and ends up not needing to do the thing you can't do.

Even if this perspective doesn't help you, it might help some other reader to keep from thinking that OOABL is "broken" and thus deciding not to go there.

Posted by bsgruenba on 21-May-2010 19:40

I created the example that I did in answer to the question that you asked about a use case. While the case I offered was admittedly contrived, it served to illustrate the point that I was trying to make - to wit, there are times when an object has to be instantiated before initialization is complete. Whether you agree with the architecture or not is irrelevant to the discussion because the argument that you are making is predicated upon an invalid assumption, and I quote: "referencing a private method from what is functionally outside the class is not something one should do."

Static members are first-class members of the class. They are not outside the class. In Java, C# and C++ (which just happen to be the three most widely used object-oriented programming languages in the world today) the distinction between a static and an instance member is that there is only one instance of a static member for all instances of the class, whereas instance members exist per instance. Furthermore, static members do not require than an instance of the class be instantiated for them to be referenced.

The assertion that static members are somehow not part of the class is completely invalid. Bjarne Stroustrup, the father of C++, makes this point in "The C++ Programming Language." Of course, static methods are not permitted access to an instance unless they specifically qualify the instance by reference, but that is to be expected.

Now all three the languages that I referenced are C derivatives. I don't know that the same is possible in SmallTalk or some of the other languages, because I know that there is a theoretical argument that goes that static are bad because they are global. I happen to think that's a load of theoretical drivel because there are plenty of cases where you end up working around the issue by creating a singleton or worse.

Be that as it may, the ABL has chosen to implement statics and it has chosen to do so in the context of class. In a class, a static member should have every one of the rights that a non-static member does.

As to your final statement, I don't think that it is a stretch to say that the OO ABL is a new language that is still growing. As such, there are bound to be design decisions that are made inside the vacuum of PSC. Some of these can (and do) turn out to be the wrong design decision. That does not mean that this decision cannot (and should not) be reversed. That does not mean the OOABL is broken - just that this decision on statics has broken the model. If the OOABL was broken, I would not be using it.

Posted by Thomas Mercer-Hursh on 21-May-2010 22:51

I have no issue about much of what you say.  I don't think there is any issue here about the static method, i.e., getMyNewClass.  The issue is about the accessibility of the non-static PRIVATE method, something that one would .... leaving statics aside for the moment, expect to be NOT accessible outside the class.  Thus, the whole idea of clss:retrieve() is something that one would never consider as being possible were it not that getNewMyClass() is a static method of the same class.

I don't dispute that one can argue both ways about whether this should or should not work.  I do suggest that there is a reasonable interpretation of why it shouldn't work and raise the possibility that PSC has made that choice.  I don't know that they have.  We won't know that until and if one of the PSC engineer types steps forward.  When and if they do, they could say that it is an unintended consequence and they will fix it or that it is an intended consequence and they won't.  As things stand, I'm not going to be upset either way because I can see the logic either way and I have no compelling use case where, not only is it the only way to get the job done, but that I am convinced that it is the right way to get the job done.

My problem here is the idea "there are times when an object has to be instantiated before  initialization is complete.".  I have wrestled with some possibly parallel situations myself and ended up convinced that this assertion is at least questionable.  Hence, my reaction to the description of the problem here is, "I wouldn't have that problem because I wouldn't do it that way".  Now, it could be that there is some other scenario in which I would think differently, but in this scenario it seems to me that issues like the resolution of the source of the data and whether or not the data even exist are issues which belong to the data layer and should be resolved before one even remotely thinks about instantiating an object.  I have recently considered some other scenarios where the source of the data was something like an EDI feed and convinced myself that parsing and validating the data in the EDI feed was a separate responsibility from the Order itself and thus the validation should occur *before* instantiating and order object ... not every possible validation, because there is some validation that is the proper business of an Order, regardless of where the data comes from, but validated enough so that one knows that one has, for example, a valid customer since there is no point in creating the order if one doesn't have a valid customer.  It is not the Order's business to have to validate the customer.

Likewise, here, it is not the Appointment's business to determine where the data comes from, to deal with missing data and fall backs, nor to deal with marking things for update.  That is all persistence stuff which should be separate from the business entity of an appointment.  Whether the data is from a persisted store or user input, an Appointment is still an appointment.

So, I am right behind you in wanting someone from PSC to step forward and let us know whether this issue you are having is by design or not and, if by design, what their reasoning is.  Then I can evaluate and respond to that reasoning.  But, regardless of what that answer might be, I wouldn't solve the stated problem any differently, regardless of the answer.  The way I would approach it, the issue doesn't exist.

Posted by bsgruenba on 21-May-2010 23:15

Again, Thomas, you are asserting that statics are not part of the class. Sorry, but that assertion is invalid. class:retrieve() is a perfectly reasonable thing to expect to be able to do inside a static method, provided that static method is part of class. As I pointed out, C++ has been doing this for over 30 years.

If the only way to instantiate a class is through static methods of that class that encapsulate the entire instantiation of the class, there is nothing wrong with the idea that an instance of a class may be instantiated before initialization is complete. There is a fundamental difference between instantiation and initialization.

I subscribe to a widely accepted school of thought in both the Java and C# communities (actually, people like Erich Gamma and Grady Booch make the same assertion) that states that you should never attempt to instantiate an object and receive null (unknown value) as the return reference, unless an exception is thrown. The second part of that is that you should never throw an exception from a constructor. That means that if you need to throw an exception when you instantiate the object, the constructor needs to be private and you need a static method for instantiation.

For what its worth, Enterprise JavaBeans have to have a constructor that takes no parameters. So do most JavaBeans and I believe (but I stand corrected) that this is true of .NET controls, too.

Finally, while I understand your argument that says that he persistence of the object is a data layer issue, I actually don't agree with you because the data layer is encapsulated in the Item class, and the client-side API *does* need to know where the data came from.

Posted by Thomas Mercer-Hursh on 22-May-2010 13:44

you are asserting that statics are not part of the class

I'm not asserting that at all.  Clearly, a static method *is* associated with the class.  The issue here is the private method, not the static method, and whether the private method is private to the class or to the object.  I am not arguing either way on which is more desirable, just pointing out that either is a reasonable point of view and, if it happens that PSC has taken the view that it is private to the object, then it isn't clear this construct should work.

class:retrieve() is a perfectly reasonable thing to expect to be able to  do inside a static method

If one was writing this in a different construct than a static and doing so inside the class, then one would write simply retrieve() or self:retrieve(), not MyClass:retrieve().

If the only way to instantiate a class is through static methods of that  class that encapsulate the entire instantiation of the class

I suppose one of the questions is, are there cases in which it is the only way.  I doubt it although I can readily understand that there are possible objections to other approaches.

There is a fundamental difference between instantiation and  initialization.

Yes, although the significance of that depends partly on philosophy.  As you say, there are those who dislike the idea of a constructor failing.  One way to achieve that is to never do anything in a constructor, at least that has a possibility of failure.  This leads some people to create init methods to do that work that might fail, but I also see the argument that there is something questionable about a public init method.  It is certainly possible to make a run-once init method and, I don't know that I accept that a public init method is necessarily bad given that it is, after all, some other object which is doing the NEW (or, in your attempted case, calling the method which amounts to doing the NEW.  But, I see nothing in this that requires a constructor to take no arguments.  E.g., if one had a queue object which could be either FIFO or LIFO depending on a setting, it would seem perfectly sensible to pass that to the constructor if one wanted it to be an immutable property of any given queue instance.  Likewise, passing the constructor data which has been validating so there is no possibility of an error seems like it is consistent with your principles.

I wouldn't consider encapsulating the data layer within a business layer business entity class to be a good example of layer separation.  Don't you also instantiate appointments based on user input?  Might you not also want to instantiate an Appointment from data from the bus?  Might you not want to generate one based on a computation?

Posted by bsgruenba on 22-May-2010 14:46

The issue here is the private method, not the static method, and whether the private method is private to the class or to the object.

Well I think it is pertinent that all C++, C#, and Java, which represent 30 years of experience in this arena, and which have each developed in almost 10 year gaps of each other, have all consistently chosen to treat access to private methods from static members of the same class the same way. There are some things that are just common sense OO, and I think this is one of them. The only reason that you are against this idea is because you do not view the static private as part of the same class as the instance private. The developers of these three languages, who all, I submit, have a lot more experience in the OO area than either you, me, or anyone at PSC, have clearly come to the same conclusion that static members should have access to private instance members. In every OO book that I have ever read (and it is no exaggeration to say that I have 15 of them on my bookshelf right now all of which I have read), there has never been any indication that there is a debate about this. In fact, my Google search turned up nothing of value on the subject at all.

If one was writing this in a different construct than a static and doing so inside the class, then one would write simply retrieve() or self:retrieve(), not MyClass:retrieve().

And your point is? It's a static method! That means there is only one instance of it for all instances of the class. You have to tell it which instance of its class you are referring to. retrieve() is the same as self:retrieve(), which effectively does exactly the same thing as MyInstance:retrieve(), except that it knows, because of its address space, what instance it is dealing with.

Yes, although the significance of that depends partly on philosophy.

And that's exactly the issue that I have with the decision that has been made according to the bug that I referred to earlier. You and I clearly have different opinions on architecture. That's nothing unusual. Throughout history people like Lorenzo Ghiberti and Filippo Brunelleschi have had massive disagreements about the "right" way to do things, and more often than not, both had very good reasons for their opinions. Whether you and I agree on our architectural decisions or not, our tools should not stand in the way of us implementing what we want the way we want to provided it adheres to the basic principles of OO.

As far as further discussion of the architectural side of things is concerned, that was not the purpose of that thread, and I'm therefore not going to continue it here.

Posted by Thomas Mercer-Hursh on 22-May-2010 15:59

Understand that I am not advocating for either instance-private or class-private.  I am acknowledging that there are arguments for both and therefore, there is a reasonable explanation for PSC's apparent choice, whether or not it is the choice you would have made.  If it was their intention to implement instance-private, then this observation of yours is not a bug, it is a design.  You may disagree with the design choice, but it is a reasonable design choice.

And, frankly, I'm not necessarily all that impressed the C#, Java, and C++ have all made the same choice.  All three languages provide abundant examples of language features which, while perceived of as useful or even essential by writers in the language, never-the-less represent dubious OO.  I mean, friend classes, really...

It seems to me that the problem here, if anything, is one of wanting more choices.  If one selects instance-private, then one is limited in a situation like this where you would like class-private behavior.  But, the reverse is true as well.  If one selects class-private, then there is no way to enforce instance-private.  That sounds to me like one instance with a reference to another instance of the same class could fiddle around with private variables in the other instance.

Rather than "fix" this problem, why not instead ask them to expand the options and add class-private and package as qualifiers?

Posted by Admin on 22-May-2010 16:13

never-the-less represent dubious OO.  I mean, friend classes, really...

Rather than "fix" this problem, why not instead ask them to expand the options and add class-private and package as qualifiers?

Do I get you right? In the same post you disqualify friend classes (where the author clearly defines who is friend and who not) as dubious and ask for package as an access modifier?

In a language where a package offers not protection? Everybody could create an ABL class in the com.cintegrity.xyz package and a package level access to methods and properties would be absolutely the same as PUBLIC! A package level access to class members would only make sense when you'd be able to "seal" a package in a .PL and nodody else could fake a member of that package.

That's a much higher requirement. So far I'd rather prefer friend where I stay more in control.

Posted by Thomas Mercer-Hursh on 22-May-2010 16:33

I am more proposing to Bruce what I think he should ask for than I am advocating a particular position.

However, I think you overstate the case against package access.  If I distribute R code for com.cintegrity.something and that R code includes no package specifications, people can add whatever they like to the package and won't have access.  The most they can access are those things which I in my code have declared to be package access.  As it happens, I doubt that I would ever do that.  Were I to do it, yes, I would have to accept that I had opened a door ... and that is one of the big reasons I wouldn't do it.  I don't think package access is terribly good OO either.

I do recognize that people make a case for such things.   I don't have to use it or support it; but that doesn't mean I have to fight it being in the language for someone else.

Posted by Tim Kuehn on 23-May-2010 07:57

bsgruenba wrote:

It's a static method! That means there is only one instance of it for all instances of the class. You have to tell it which instance of its class you are referring to.

Perhaps I'm getting static classes and static methods confused here - because when I read this my first thought on reading this was "there are no other instances to tell it that you're referring to!"

Could you elaborate on this a bit more?

Posted by Thomas Mercer-Hursh on 23-May-2010 10:29

ABL doesn't have static classes, only static members.  If a class has only static members, then you can consider it a static class in effect, but technically it isn't one.  If there is a single non-static member, then there will be an instance for each NEW.  Bruce is _not_ doing a variation on singleton here, but rather he is trying to encapsulate the actions necessary to instantiate and initialize and object within the object itself and to do so without putting that initialization code in the constructor.  He doesn't want to put it in the constructor because many people consider it bad form to have anything in the constructor which might fail and thus result in the object not being created.  I.e., a constructor should never return ?.  In this case, the retrieve() action has the potential to fail and so he wants to execute it in a place where he can catch the error and handle it cleanly.  That would be easy if retrieve were public, but he wants it to be run only once and not be exposed to other classes.

OK summary Bruce?

Posted by bsgruenba on 23-May-2010 10:35

Hi Tim,

Unless the static method already has a reference to an existing instance of an object, it obviously cannot refer to that instance because each instance has its own reference. But, if you take the singleton pattern as an example, you have a static variable that is defined to hold the instance value. When the public instance property is used, it returns the value of the static instance variable which points to an instantiated object.

So:

CLASS Singleton: 
   
    DEFINE PRIVATE STATIC VARIABLE snInst  AS Singleton NO-UNDO. 
   
    DEFINE PUBLIC STATIC PROPERTY Instance AS Singleton  NO-UNDO
      GET:
        IF NOT VALID-OBJECT(snInst) THEN
          snInst = NEW Singleton().
        RETURN snInst.
      END.         
  
    CONSTRUCTOR PRIVATE Singleton():
    END.
END CLASS.

This is the typical code around a Singleton. snInst is a private static variable that holds the reference to the only instantiated copy of Singleton. When the get on the Instance property is called, it first checks to see if the object exists and if not, it instantiates it by calling a private constructor. The Instance property uses snInst to determine which instance of the Singleton class (of which there is only one) it should return.

Now lets expand this a little. Within that Instance property's get, I have a reference to the instance of Singleton that is instantiated and because it is an object of class Singleton, I should be able to call a private member on it from within the get.

To show you how flawed and inconsistent the reasoning is, a constructor is nothing more than a method that is called to instantiate an object. And yet, the ABL allows you to call a private constructor from a static, but it won't allow you to call private methods. The argument is that the static portion of the class is somehow distinct from the non-static portion of the class and that you would be violating encapsulation by allowing access to private methods, but in this case the constructor is a private method and it can be called from a static method/property. If a static member is not part of the same instance, then it should not be allowed to call the constructor and Singletons would not be possible in the ABL.

Furthermore, if statics are not part of the same instance of the class, instance members should not be able to refer to or modify private static members, but the following code not only compiles, but works as expected:

CLASS ThatClass: 
    DEFINE PRIVATE STATIC VARIABLE instCount AS INTEGER NO-UNDO.

    DEFINE PRIVATE VARIABLE myInstNum AS INTEGER NO-UNDO.

    CONSTRUCTOR ThatClass():
        instCount = instCount + 5.
        myInstNum = instCount.
    END.
   
    DEFINE PUBLIC PROPERTY InstanceNumber AS INTEGER NO-UNDO
        GET:
          RETURN myInstNum.
        END.
END CLASS.

As expected each time I instantiate ThatClass, I get a myInstNum that is 5 greater than the last one that was called. So we'll expand Singleton a little:

CLASS Singleton: 
   
    DEFINE PRIVATE STATIC VARIABLE snInst  AS Singleton NO-UNDO. 
   
    DEFINE PUBLIC STATIC PROPERTY Instance AS Singleton  NO-UNDO
      GET:
        IF snInst = ? THEN
          snInst = NEW Singleton().
        RETURN snInst.
      END.         
  
    CONSTRUCTOR PRIVATE Singleton():
        DEFINE VARIABLE that AS ThatClass NO-UNDO.
        DEFINE VARIABLE iCount AS INTEGER NO-UNDO.
        DO iCount = 1 TO 10:
            that = NEW ThatClass().
            MESSAGE that:InstanceNumber
                VIEW-AS ALERT-BOX.
        END.
    END.
END CLASS.

Now, when Singleton is instantiated, it instantiates 10 instances of ThatClass and displays a message with the instance number. This code works exactly as expected. But here's the thing: In the constructor for Singleton, I cannot (and should not) be able to refer to the private static instCount variable in ThatClass. The constructor for Singleton is not a member of ThatClass. In the same way, ThatClass cannot and should note be able to instantiate a copy of Singleton. It has to go through Singleton:InstanceMember to do so.

The point is that there is no such thing as instance-private. It is a cop-out.

Posted by bsgruenba on 23-May-2010 10:42

Yeah. Fair summary. Just one other point I'd like to mention, and I ran into this last night with some Java stuff I am trying to do.

One of the reasons that many frameworks require a public, no-parameter constructor is for reflective purposes. So, for example, in Java you can annotate classes to indicate how the class should be serialized to XML if you are using JAXB 2.0. In order to follow your annotations, JAXB instantiates an instance of the class to reflect on the members and the annotations that you added, even though it does nothing with them. So JAXB requires a public, no-parameter constructor. This is true of hundreds of frameworks like this in the Java and C# communities.

Posted by Peter Judge on 24-May-2010 07:00

Understand that I am not advocating for either instance-private or class-private.  I am acknowledging that there are arguments for both and therefore, there is a reasonable explanation for PSC's apparent choice, whether or not it is the choice you would have made.  If it was their intention to implement instance-private, then this observation of yours is not a bug, it is a design.  You may disagree with the design choice, but it is a reasonable design choice.


This was discussed in the 10.2B Beta forum. I've attached a PDF of the relevant thread for those that weren't on the Beta, and who are interested.

-- peter

Posted by bsgruenba on 24-May-2010 11:09

Hi Peter,

Thanks for posting this. I was not in the beta so was not aware of this thread until it was shared.

To all (ie, this is not aimed at Peter):

I read, with interest, Alex Herbstritt's comments that were supported by Shelley Chase. I have immense respect for both Alex and Shelley and I understand that there are decisions that have to be made for  the good of the product. Having said that, Alex's explanation of instance-private is fundamentally flawed as I alluded to in my other post that describes the singleton example.

Alex refers to Ruby as an example of a so-called "instance-private" language, and, yes, his example is correct. In Ruby, you cannot call private members from static members. On the other hand, Ruby has a completely different protection model around private from anything like C++, Java or C#.

First, Ruby does not have the equivalent of private. Private in Ruby is the same as protected in Java/C#/C++/OpenEdge.

Second, Ruby allows you to modify the  protection of a method at any time, including runtime. In Java/C#/C++/OpenEdge, those are compile-time decisions.

Third, Ruby is a completely dynamic language - which is where C# is moving. The OOABL is very compile-time constrained.

So Alex's comparison of the ABL's instance-private protection with Ruby's is a comparison of apples against oranges. This comparison would far more accurate if the comparison was being drawn with the super-procedure model in the non-OOABL.

And that leads me to believe that it is the underpinnings of the OOABL that are what is causing a problem. I have a feeling that the OOABL is the way it is because if it were changed, it would break existing functionality in the 4GL. If that is the reason that this decision was made, just say so. If not, then fix it because it is not consistent.

Posted by Thomas Mercer-Hursh on 24-May-2010 11:31

Peter, I think it would be very useful if someone who knew about the rationale for this design decision would speak up here.  It would help a lot to know if there is some aspect of the AVM which made this choice necessary and/or, if it was just a question of a design choice, what motivated that choice.  At the least, there do seem to be some inconsistencies here, so it would be good to know whether PSC perceived these as consistent or not.

Posted by Shelley Chase on 24-May-2010 13:55

Your observations are all correct that ABL private static class members are instance-based. This limitation is, as Bruce suggested, a result of the implementation of OOABL classes alongside persistent procedures. Constructors which are a new concept for ABL was implemented to support class-based scoping for private constructors. So yes there is an inconsistent behavior here.

Classed-based scoping for private members is on our OO roadmap and is being prioritized and scoped along with other features and bug fixes for Release 11.

Thanks for the lively discussion ;-)

-Shelley

Posted by bsgruenba on 24-May-2010 14:04

Thanks, Shelley. This is great feedback and news.

Posted by Thomas Mercer-Hursh on 24-May-2010 14:11

After some additional consideration, I have to agree that class private makes the most sense, even though I wouldn't use it in the way it was used in the original example, so it is nice to hear that we are headed in  that direction.

Posted by Tim Kuehn on 25-May-2010 07:44

bsgruenba wrote:

...

The point is that there is no such thing as instance-private. It is a cop-out.

Thanks for the detailed explanation - it was quite illuminating!

Posted by Admin on 03-Aug-2011 12:12

A bit late to the party and I can even take the blame for a duplicate post (http://communities.progress.com/pcom/message/134345) so... back to the roots

Should we take from Shelley's comment that classed-based scoping for private members are going to be implemented and that the so-called bug in 10.2A is to be reverted?

Is accessing an instance private member from another instance going to be possible?

class Foo :

   method private void _Test ():

   end method.

   method public void Test (obj as Foo):

      obj:_Test().

   end method.

end class.

For reasons I can clearly name I do find accessing private instance members from static members of the same class while accessing the same private instance members from other instance doesn't appeals me that much

Being consistent about it as private constructors are alowed from static members then it means all private instance members should be accessible as well and maybe this need to be true for all private instance members from the class (even when called from other instance)... I know in Java is implemented like that and probably same thing in .Net but still... I can't put my finger on it

This thread is closed