Here is an expanded example...
 

Method logProps(parent) As %String [ CodeMode = objectgenerator ]
{
  set x=%code
  for i=1:1:%compiledclass.Properties.Count() {
    #dim As %CompiledProperty
    set p=%compiledclass.Properties.GetAt(i)
    set name=p.Name
    if $extract(p.Name)'="%" do x.WriteLine(" do ..logProp("""_p.Name_""",.."_p.Name_")")
  }
}

Method logProp(propName, propValue)
{
  // call your macro logger here, or replace call to logProp with your macro above
}


Let's say you had a property...

  Property firstName As %String;


When you compile the class, your logProps method would look like this...

zlogProps(parent) public {
  do ..logProp("firstName",..firstName) }


There is an article on DC that explains method generators in more depth..

https://community.intersystems.com/post/exploring-code-generation-cach%C3%A9-method-generators

Hi Alexandr,

If property is in the context of this class then you could try

set value=$property($THIS,propName)

If you make the code blocks outer method a generator, e.g.  [ CodeMode = objectgenerator ]

then you can bake the property accessor into the underling INT code, e.g.

do %code.WriteLine(" set value=.."_propName)

this approach means you don't have to make any repetitive IO calls to dictionary at run time.

If you need an expanded example then I can bash something out.

Sean.

Hi Tomas,

Its a good point, but performance doesn't have to be an issue.

In the main, the lint tool would only need to check the class that is being edited and compiled at that time. If coded optimally (e.g. memoize dictionary lookups etc) then is should only add milliseconds to a compilation cycle. This would hardly be noticed by the developer.

There is a use case where the edited class could break implementations of that class. In this instance a "lint all" process could be hooked into various other non disruptive steps, such as running a full unit test.

Losing milliseconds at compile time is a small trade off to the collective time lost on supporting these types of errors.

All of the uses cases are around the implementation of instance methods and static methods.

In all cases class A either extends or uses class B. For class A to compile, class B must be present and compiled first, no matter what module or namespace it belongs to. If class B implements a generic interface, then all variances of class B should adhere to the same interface (despite being a manual verification check at the moment).

The types of examples you have raised sound like you are hot calling functional code, in which case I agree it would be very hard to lint this style of coding.

Hi Clark,

Can you provide a more concrete example.

In particular, why would mappings change the formal spec or return type of a method.

Are you using mappings in some kind of environmental polymorphism?

For me the implementation should never be affected by mappings. Mappings are just a convenience, not a logical influence.

I'm struggling to understand the limitation that you are suggesting?

That's very true, rawContent is limited to 10,000 characters.

If you have messages that are larger then you could do it this way...

ClassMethod DisplaySegmentStats()
{
  write !!,"Segment Statistics...",!!
  &sql(declare hl7 cursor for select id into :id from EnsLib_HL7.Message)
  &sql(open hl7)
  &sql(fetch hl7)
  while SQLCODE=0
  {
    set msg=##class(EnsLib.HL7.Message).%OpenId(id)
    set raw=msg.getSegsAsString(id)
    for i=1:1:$l(raw,$C(13))-1
    {
      set seg=$p($p(raw,$c(13),i),"|")
      set stats(seg)=$G(stats(seg))+1
    }
    &sql(fetch hl7)
  }
  &sql(close hl7)
  zw stats
}

Hi James,

Nothing simple that I can think of (perhaps DeepSee?).

Alternatively, I normally bash out a few lines of code, something like this... 

ClassMethod DisplaySegmentStats()
{
  write !!,"Segment Statistics...",!!
  &sql(declare hl7 cursor for select rawContent into :raw from EnsLib_HL7.Message)
  &sql(open hl7)
  &sql(fetch hl7)
  while SQLCODE=0
  {
    for i=1:1:$l(raw,$C(13))-1
    {
      set seg=$p($p(raw,$c(13),i),"|")
      set stats(seg)=$G(stats(seg))+1
    }
    &sql(fetch hl7)
  }
  &sql(close hl7)
  zw stats
}

Sounds like you might be adding unnecessary complexity.

> What is the most efficient way to process this large file?

That really depends on your definition of efficiency.

If you want to solve the problem with the least amount of watts then solving the problem with a single process would be the most efficient.

If you add more processes then you will be executing additional code to co-ordinate responsibilities. There is also the danger that competing processes will flush data blocks out of memory in a less efficient way.

If you want to solve the problem with speed then its important to understand where the bottlenecks are before trying to optimise anything (avoid premature optimisation).

If your process is taking a long time (hours not minutes) then you will most likely have data queries that have a high relative cost. It's not uncommon to have a large job like this run 1000x quicker just by adding the right index in the right place.

Normally I would write a large (single) process job like this and then observe it in the management portal (System>Process>Process Details). If I see its labouring over a specific global then I can track back to where the index might be needed.

You will then get further efficiencies / speed gains by making sure the tables are tuned and that Cache has as much configured memory cache as you can afford.

If you are writing lots of data during this process then also consider using a temporary global that won't hit the transaction files. If the process is repeatable from the file then there is no danger of losing these temp globals during a crash as you can just restart the job after the restore.

Lastly, I would avoid using Ensemble for this. The last thing you want to do is generate 500,000 Ensemble messages if there is no need to integrate the rows of data with anything other than internal data tables.

Correction. It's perfectly fine (for Ensemble) to ingest your file and process it as a single message stream. What I wouldn't do is split the file into 500,000 messages when there is no need to do this. Doing so would obviously cause additional IO.