TT in method

Posted by Thomas Mercer-Hursh on 09-May-2015 11:54

Just curious ... what is the rationale behind not allowing the definition of a TT in a method?  I can see that it might be potentially expensive if the method is called a lot, but it also seems that there are contexts in which the proper scope of the TT is the method (you can probably tell that I just encountered one).

Posted by Laura Stern on 10-May-2015 11:23

Correct.  Details would be way too obscure to explain.

All Replies

Posted by Mike Fechner on 09-May-2015 12:12

You should log an idea to get that fixed ;-)

Von meinem Windows Phone gesendet

Von: Thomas Mercer-Hursh
Gesendet: ‎09.‎05.‎2015 18:54
An: TU.OE.Development@community.progress.com
Betreff: [Technical Users - OE Development] TT in method

Thread created by Thomas Mercer-Hursh

Just curious ... what is the rationale behind not allowing the definition of a TT in a method?  I can see that it might be potentially expensive if the method is called a lot, but it also seems that there are contexts in which the proper scope of the TT is the method (you can probably tell that I just encountered one).

Stop receiving emails on this subject.

Flag this post as spam/abuse.

Posted by Tim Kuehn on 09-May-2015 12:14

TT's are scoped to the enclosing procedure or class, not a method, so it make sense to have the definition at the class level. 

[collapse]
On Sat, May 9, 2015 at 12:54 PM, Thomas Mercer-Hursh <bounce-tamhas@community.progress.com> wrote:
Thread created by Thomas Mercer-Hursh

Just curious ... what is the rationale behind not allowing the definition of a TT in a method?  I can see that it might be potentially expensive if the method is called a lot, but it also seems that there are contexts in which the proper scope of the TT is the method (you can probably tell that I just encountered one).

Stop receiving emails on this subject.

Flag this post as spam/abuse.




--
Tim Kuehn:  Senior Consultant  - TDK Consulting Services
President - Ontario PUG 
Program Committee Chair - PUG Challenge Americas, 
Course Instructor: Intro to OO Concepts for Procedural Programmers

Skype: timothy.kuehn
Ph: 519-576-8100
Cell: 519-781-0081
[/collapse]

Posted by Thomas Mercer-Hursh on 09-May-2015 12:31

But, Tim, that is my question.  I have a case where the logical scope is the method.  I am parsing a define shared statement and if the statement turns out to be about a temp-table, then I want to parse the fields.  I need a TT to save that information in until I get to the end and am ready to build the database records.  So, the scope is logically the method. Scoped to the class, I have to empty it every time.

Posted by Tim Kuehn on 09-May-2015 13:50

My suggestion then is to create a holder class for the TT and use that.

Weren't the one who has always advocated encapsulation for TT's? :)



Sent from my BlackBerry 10 smartphone on the Fido network.
[collapse]
From: Thomas Mercer-Hursh
Sent: Saturday, May 9, 2015 13:32
To: TU.OE.Development@community.progress.com
Reply To: TU.OE.Development@community.progress.com
Subject: RE: [Technical Users - OE Development] TT in method

Reply by Thomas Mercer-Hursh

But, Tim, that is my question.  I have a case where the logical scope is the method.  I am parsing a define shared statement and if the statement turns out to be about a temp-table, then I want to parse the fields.  I need a TT to save that information in until I get to the end and am ready to build the database records.  So, the scope is logically the method. Scoped to the class, I have to empty it every time.

Stop receiving emails on this subject.

Flag this post as spam/abuse.


[/collapse]

Posted by Thomas Mercer-Hursh on 09-May-2015 14:01

It is in a class.  In fact, the point is that I want it as a method in the class.  It is an implementation detail, not something in the problem space and thus does not deserve a class on its own.

Posted by Tim Kuehn on 09-May-2015 14:08

So all your "implementation details" are not in a class?

Sent from my BlackBerry 10 smartphone on the Fido network.
[collapse]
From: Thomas Mercer-Hursh
Sent: Saturday, May 9, 2015 15:02
To: TU.OE.Development@community.progress.com
Reply To: TU.OE.Development@community.progress.com
Subject: RE: [Technical Users - OE Development] TT in method

Reply by Thomas Mercer-Hursh

It is in a class.  In fact, the point is that I want it as a method in the class.  It is an implementation detail, not something in the problem space and thus does not deserve a class on its own.

Stop receiving emails on this subject.

Flag this post as spam/abuse.


[/collapse]

Posted by Thomas Mercer-Hursh on 09-May-2015 14:21

????

The TT is one of the implementation details of the class.  The class is BuildShared.cls, a reasonable part of the problem space of ABL2DB which build database records for shared objects using Proparse.  The TT is a temporary holder for the fields of a temp-table until one gets to the end of the method which is parsing the define shared whatever statement.  When it gets to the end of the statement, it builds the SharedObject record, the SharedTempTable record when the object is a temp-table, and the SharedTempTableField records belonging to the temp-table which have been stored in the TT in question.  The TT in question is not a part of the problem space, merely of the implementation of the BuildShared class.

Posted by pedromarcerodriguez on 09-May-2015 14:30

A generic holder class for TTs it is probably a good practise for those sort of scenarios.

Another option is to use some other data structure, you have quite a few options in OpenEdge.Core.Collections package.

Posted by Thomas Mercer-Hursh on 09-May-2015 14:55

A TT is a very appropriate data structure for the requirement.  I have 4 fields and 2 sort orders ... not something easily done with a collection, but a real natural for a TT.  In the top half of the method there is a big case statement based on the type of object where, when the shared object is a TT, I collect the fields.  In the bottom half of the method I create the common shared record for all shared objects and another big case statement on the object type to create the sub tables.  In the one for temp-table I create the record for the temp-table itself and for the fields of that temp-table.  I.e., the scope of use is from the top half to the bottom half of the method and the nature of the data and the access to that data is right up the alley of a TT.  With the TT defined at the class level, I can empty it every time the method is executed and this works, but is less elegant than if the TT were scoped to the method.  If I were to put the TT into an object ... which I would be reluctant to do because it is an implementation detail of the BuildShared.cls, not a problem space entity, the class could either be scoped to the class or the method.  If scoped to the class, there would be no advantage over what I have now.  If scoped to the method, I would have to create one for either every execution of the method, one for every defined shared in the entire code base, or I would have to only create it for define shared temp-table, which might be more manageable, but which is, at the least, significantly more overhead and code than the perfectly simple TT, which is well suited to the task.  Collections would not be well suited because then I would need a field object for every field in every shared temp-table and the collection would need to be sequenced two different ways.  So, no, I don't think something besides a temp-table would be good practice.

Posted by pedromarcerodriguez on 09-May-2015 15:19

What data structure is the correct one for your requirement, it is obviously something I am not going to argue, without knowing the requirement itself, even when the argument that a TT is the appropriate data structure would probably apply to almost everything, and that doesn't make it the data structure I would use in every case.

What I don't understand is your point about putting TT into a class holder, if you have a holder class for the TT that you define and create inside your method then you basically get what you are looking for, having the TT scoped to your method. If you make that class generic to hold any Temp table as a helper class where the TT itself is dynamically created and can hold any TT, you can reuse in many other instances.

Now, what it is best for your particular scenario, no doubt, you know best, so my opinion is just based as a general approach, not for your particular method.

Posted by Thomas Mercer-Hursh on 09-May-2015 15:43

I have no problem wrapping a TT that implements something in the problem space as a class ... indeed, I advocate it.  Here, though, it would create a lot of indirection that has no problem space value since the TT is an implementation artifact, not a problem space entity.

Moreover, as noted, if an object and scoped to a method, this means I might potentially be creating lots and lots of tiny little objects ... not something that the AVM is very good at.  Now, in this particular case, one hopes it isn't too many because I am building data structures to record defined shared statements and we hope that there aren't a lot of those and particularly a lot of defined shared temp-tables and work-tables.  But, it wouldn't surprise me to find an application where there were a lot and I have similar requirements in methods which are very common.

Posted by pedromarcerodriguez on 09-May-2015 15:55

I think we might be seeing this holder class differently, simplifying it to an extreme this is what I think about it. This would be a class that in its constructor would create the TT dynamically (details of how to actually specify the schema I would have to think about), and it would expose a list of methods to access and set fields of the TT, create, delete and retrieve records. All of that doesn't necessarily need more objects that the holder class itself, the TT and possibly a query.

Of course there would be a lot of instances of this object potentially, but that wouldn't be different if as you asked initially the TT was defined at method level, so can't see a big difference between your request and the holder class approach.

And to me, the ease of mind of having the TT at the correct scope, would be enough even to sacrifice some performance.

Posted by Thomas Mercer-Hursh on 09-May-2015 16:14

Making it generic would, I think, make it way too complex.   Making it a defined to the purpose fixed class would *much* more straightforward.  But, also to dubious benefit since it is not a problem space entity.

Scoping the TT to the method wouldn't necessarily mean having to reallocate from memory every time.  I see no reason that the structure couldn't be there for the duration of the class and it simply know to start empty and finish empty on each method run.  I.e., basically the same thing that is happening now but where the intent to empty is implicit in the scoping.

I'm afraid it might be a lot of performance ... and in a function that is not blistering fast as it is.  The TT is awfully simple in use.

This, for example, is the entire use during the set part of the method

            create mbfttField.
            assign
                minOrder = minOrder + 1    
                mbfttField.inOrder = minOrder
                mbfttField.chName = mobSubToken:getText()
                .
            mobSubToken = mobSubToken:nextSibling().  
            if NodeTypes:getTypeName(mobSubToken:GetType()) = "AS"
            then do:
              mobSubToken = mobSubToken:firstChild().
              mbfttField.chDataType = NodeTypes:getTypeName(mobSubToken:GetType()).
            end.
            else if NodeTypes:getTypeName(mobToken:GetType()) = "LIKE"
            then do:
              mobSubToken = mobSubToken:firstChild().    /* FIELD_REF */
              mobSubToken = mobSubToken:firstChild().    /* ID */
              mbfttField.chLike = mobSubToken:getText().
            end.


and this is the use

          for each mbfttField by mbfttField.inOrder:
            do for mbfSharedTempTableField:
              create mbfSharedTempTableField.
              assign
                  mbfSharedTempTableField.chID = guid
                  mbfSharedTempTableField.chSharedTempTableID = mbfSharedTempTable.chID   
                  mbfSharedTempTableField.chName = mbfttField.chName
                  mbfSharedTempTableField.chDataType = mbfttField.chDataType
                  mbfSharedTempTableField.chLike = mbfttField.chLike
                  .       
            end.
          end.


Posted by pedromarcerodriguez on 09-May-2015 16:49

But if the structure is there and one method calls itself recursively (for instance) how does it manage that create/delete, I think that would very tricky. 

As per being a way too complex task, I disagree, check a quick implementation of what a TTHolder class would look like, just put it together, far from complete, but already functional.

USING Progress.Lang.*.

BLOCK-LEVEL ON ERROR UNDO, THROW.

CLASS TTHolder: 

    DEFINE PRIVATE VARIABLE hTT     AS HANDLE.
    DEFINE PRIVATE VARIABLE hBuffer AS HANDLE.
    DEFINE PRIVATE VARIABLE hQuery  AS HANDLE.
    
    CONSTRUCTOR PUBLIC TTHolder (INPUT hSchema AS HANDLE):
        CREATE TEMP-TABLE hTT.
        hTT:CREATE-LIKE (hSchema).
        hTT:TEMP-TABLE-PREPARE ("TTHolder").
        hBuffer = hTT:DEFAULT-BUFFER-HANDLE.
        CREATE QUERY hQuery.
        hQuery:SET-BUFFERS(hBuffer).        
    END CONSTRUCTOR.    

    METHOD PUBLIC LOGICAL isAvailable():
        hBuffer:AVAILABLE.
    END METHOD.
    
    METHOD PUBLIC VOID createRecord():
        hBuffer:BUFFER-CREATE ().
    END METHOD.        

    METHOD PUBLIC VOID deleteRecord():
        hBuffer:BUFFER-DELETE ().
    END METHOD.        

    METHOD PUBLIC VOID setValue(pcField AS CHARACTER, pcValue AS CHARACTER):
        hBuffer:BUFFER-FIELD (pcField):BUFFER-VALUE() = pcValue.
    END METHOD.        

    METHOD PUBLIC VOID setValue(pcField AS CHARACTER, piValue AS INTEGER):
        hBuffer:BUFFER-FIELD (pcField):BUFFER-VALUE() = piValue.
    END METHOD.        

    METHOD PUBLIC VOID setValue(pcField AS CHARACTER, pdValue AS DATE):
        hBuffer:BUFFER-FIELD (pcField):BUFFER-VALUE() = pdValue.
    END METHOD.        

    METHOD PUBLIC CHARACTER getValue(pcField AS CHARACTER):
        RETURN hBuffer:BUFFER-FIELD (pcField):BUFFER-VALUE().
    END METHOD.        

    METHOD PUBLIC DATE getValueDate(pcField AS CHARACTER):
        RETURN hBuffer:BUFFER-FIELD (pcField):BUFFER-VALUE().
    END METHOD.        

    METHOD PUBLIC INTEGER getValueInteger(pcField AS CHARACTER):
        RETURN hBuffer:BUFFER-FIELD (pcField):BUFFER-VALUE().
    END METHOD.        

    METHOD PUBLIC VOID openQuery (pcWhere AS CHARACTER, pcSort AS CHARACTER):
        hQuery:QUERY-PREPARE("FOR EACH TTHolder " + 
                             "WHERE " + pcWhere + 
                             " " + pcSort).
        hQuery:QUERY-OPEN ().
        hQuery:GET-FIRST().                     
    END METHOD.    
    
    METHOD PUBLIC LOGICAL hasNext():
        hQuery:GET-NEXT().
        RETURN NOT hQuery:QUERY-OFF-END.
    END METHOD.
    
END CLASS.

And an example of its use:

USING TTHolder.

def temp-table ttbuffer 
     fields inOrder as integer
     fields chName  as character
     fields chLike  as character.

def var ttTest as TTHolder no-undo.

ttTest = NEW TTHolder(TEMP-TABLE ttBuffer:DEFAULT-BUFFER-HANDLE).

ttTest:createRecord().
ttTest:setValue("inOrder",1).
ttTest:setValue("chName","First").

ttTest:createRecord().
ttTest:setValue("inOrder",2).
ttTest:setValue("chName","Second").

ttTest:openQuery("","BY inOrder").

REPEAT:
    MESSAGE ttTest:getValueInteger("inOrder") VIEW-AS ALERT-BOX.
    MESSAGE ttTest:getValue("chName") VIEW-AS ALERT-BOX.
    IF NOT ttTest:hasNext() THEN LEAVE. 
    
END.    


Posted by Thomas Mercer-Hursh on 09-May-2015 17:10

Well, recursive is clearly a different animal, but I think a special case.  I have a recursive method in this class, but it is very simple and doesn't have any of these issues.

Naturally, I can't really run a performance comparison since the compiler won't let me do what I think I want to do.  It would be interesting to run a performance comparison to a single TT with empty per method.  Want to bet it would be real ugly?  One new object per method call.  5 method calls per record to set.  Vs an empty per method and a create and assign.  It would be faster without the dynamic stuff.

Color me skeptical, as well as making the code much more obscure.

Posted by pedromarcerodriguez on 09-May-2015 17:41

Running a probably meaningless performance test over the example I provided earlier, the OO solution is approximately 3 times slower than a direct use of the Temp Table.

Test scenario I ran is creating 20000 records in the TT, and later traversing those 20000.

Results have been around 600-700 ms for OO, 180-200 direct use of the TT.

If that is a performance problem or not, of course is something to decide in every particular case, in most cases I think I would sacrifice this performance for the advantages of an OO approach.

And I don't see as more obscure, I see it more consistent with the rest of OO code.

Posted by ske on 10-May-2015 06:18

> check a quick implementation of what a TTHolder class would look like,

> just put it together, far from complete, but already functional.

Why can't you, in Thomas' specific case, just use a DEFINE TEMP-TABLE with a fixed schema in the helper class, expose it publicly, and then access the TEMP-TABLE directly from the method of the other class where Thomas needs the TEMP-TABLE? Or return a handle to the TEMP-TABLE, to access it directly.

That would break the idea of each class only manipulating it's own members, and it would not be re-usable for other TEMP-TABLEs, but it seems to me it would be an even simpler and quicker work-around for what Thomas requested. Or is there something that would prevent doing this? I haven't tried it, as I'm stuck with a much older version.

Posted by Laura Stern on 10-May-2015 06:56

You cannot define a public temp-table in a class.  It is due to implementation issues within the AVM, not philosophical ones. And I believe that this is also the answer to the original question of why you can't define a temp-table in a method.  

Posted by ske on 10-May-2015 08:56

> You cannot define a public temp-table in a class

Ok. But even without being able to make the temp-table public, it is still possible to return a handle to it, or return the temp-table as an output parameter with the BIND option and use it with REFERENCE-ONLY, according to this article: knowledgebase.progress.com/.../P189649

Posted by Thomas Mercer-Hursh on 10-May-2015 09:23

Pedro, if you still have the code, try it again for something like 10 rows in the TT.  With 20,000 in the TT, the actual TT updating that is happening in both versions is likely to dominate.  Besides, my use case is for small tables.

Posted by Thomas Mercer-Hursh on 10-May-2015 09:24

ske, that would be ***way*** down on my list because it is exposing the interior implementation of the class to the world.

Posted by Jeff Ledbetter on 10-May-2015 10:11

The rationale is probably the same as the rationale for not being allowed to define a temp-table in a function or internal procedure.

Posted by ske on 10-May-2015 10:19

Seems like you want to have the cake and eat it too. You wanted to use the temp-table internally in your method, with no time-consuming helper class wrapping all access to it. This is a work-around to do that, when ABL does not provide a direct way of doing that. And now you are concerned that this trick will expose implementation of the temp-table to your method, which you wanted it exposed to in the first place...

Think of the helper class as a "friend" class to your original class.

It's not like any other class can gain access to this temp-table anyway, even with this trick. Your original class will have it's own instance of this temp-table (by instantiating the helper class), just like a local temp-table.

Posted by pedromarcerodriguez on 10-May-2015 10:20

WIth 10 rows, and 2000 calls to the test procedure results are around 2000ms for holder class, 400ms for direct use of the TT.

I am testing with -q parameter to stop interference from searching propath, but this is a very crude version and it could be improved surely..

I would still got that route personally unless performance was absolutely critical, but thats just personal taste.

Posted by Thomas Mercer-Hursh on 10-May-2015 10:40

@ske, I don't see it that way at all.  To me, the TT is no different than a variable I define in a method because that is its appropriate scope.  Conceptually, that is what one would expect to do and it appears that it is an AVM limitation that keeps one from doing so.  This whole business of moving it out into another class is adding a layer of complexity that has no value other than to guarantee the limitation of scope.  The work around is to remember to empty the table each time.  The issue of exposing internal implementation is not one of trying to hide the implementation from the method, since if things worked as they wish they did, the TT would be right there in the method, but rather than it is bad OO practice for any object to expose its internal implementation ... which your proposal would do by design.  Pedro's design is a good one in general since he is doing exactly what I advocate with TTs, particularly those which need to be shared between objects.  Passing a TT as a parameter, one is exposing implementation.  Passing an object which contains a TT one is not.  I just think it is the wrong solution here because the TT isn't being passed anywhere, it is local to the method of this one class.

Posted by Thomas Mercer-Hursh on 10-May-2015 10:48

@pedro, 5X is not trivial for a method that is going to be called on every define statement in every program in the entire application.

As I said above, I like the basic idea ... it is exactly how I would encapsulate a TT that needed to be used in more than one place (except for the use of LIKE, which I abhor).  It is just that here it seems to be adding a layer of complexity which is not needed to a usage which is inherently very simple.  The TT is an implementation detail of one method in one class.  While I would prefer to express that directly by defining the TT in the method, moving it to the class level only really means that I need to remember to empty it.   It is possible that, were one able to define it in the method, that the overhead of instantiating it on each execution would be a problem.  What I would want the AVM to do is to instantiate it once and the mark it unavailable or available depending on whether it was in the method and to handle the implied empty.  But, if I can't have that, I prefer what I am doing to the extra layer of complexity.

Posted by Laura Stern on 10-May-2015 11:23

Correct.  Details would be way too obscure to explain.

Posted by ske on 10-May-2015 13:04

I totally agree that it would be preferable for ABL to allow local temp-tables in methods.

And if I would choose between our suggestions and just keeping the temp-table at the class level, I too would choose the class level.

It just seemed to me, originally, that you were objecting so strongly to having to do that, that it seemed to be interresting to come up with various other alternatives that would avoid keeping it at the class level. (Which is easier for us to do than changing ABL.) But of course there will be some trade-offs in doing so.

I do accept your objections to these suggestions as motivation for desiring real local temp-tables in ABL. I don't want to detract from that. And I don't really see our suggestions as being better than keeping the temp-table at the class level.

- But in general, in other cases, for my own part, I would now and then accept some flexibility or "creativity" when interpreting the principles of "good" OO design, if it allows me to get closer to the gist of these same principles, and if there were no other work-arounds. (E.g if there were no class level temp-tables either, or if I needed recursion too.) I'm used to having to make do with whatever the current language provides. And it can be a bit of fun, too.

For instance, if you would just pretend that the helper class in my suggestion is not a "real" class of its own, then it all works out just like a local temp-table, good OO principles notwithstanding. I don' see much of a risk for problems in practice with that, even if at some literal level it may be seen as breaking the principles.

And the concept of "friend" classes with access to each other's implementation is not new, and have many uses, although each one can be debated, of course. Although ABL is lacking a FRIEND option too, as well as local/private classes inside classes, which would make this work-around less public.

Regarding passing temp-tables as parameters exposing the implementation, does that mean you don't like passing record buffers either? What about passing structs around, in other languages?

I can see the point of only passing around objects, but I'm not quite that strict myself.

Posted by Thomas Mercer-Hursh on 10-May-2015 13:37

Naturally, if there is no other way to get something done, then one does what one can ... inserting apologies into comments so that a maintainer will understand the problem, in case the limitation goes away in the future.

And, while in general maintenance trumps small amounts of performance, here we have both favoring the simpler solution.  This function takes about 12 minutes to process all of the code in the sample code base.  Making that an hour wouldn't be deadly, but it wouldn't be something I was likely to do unless I was getting greater value out of it.

And, no, in general I would not pass a buffer, either, particularly between subsystems.

Posted by Thomas Mercer-Hursh on 10-May-2015 13:38

BTW, I do wish that one could define stand alone buffers not attached to tables.  Those I would happily pass.

Posted by Thomas Mercer-Hursh on 11-May-2015 09:41

BTW, now that this class is mostly functioning ... emphasis on the mostly ... it is taking 3 hours to get through the whole code base, so a 5X increase would definitely be a problem.

This thread is closed