Sean Connelly · Mar 24, 2017 go to post

Nice article Eduard.

Re: last comment. Not sure how having to change the implementation of a Macro is any less of a pain than having to change hundreds of class method arguments. If anything Macro's are a great way to abstract application wide changes with a single line of code. Granted recompilation is required, but code that can't withstand recompilation all day long has a deeper problem.

My only negative about Macro's is when you can't read the code for Macro soup, less is more for me. 

One tip to add. I like to have auto complete work for Macros. If you precede each macro with a triple comment, then they will appear in the auto complete suggestions...

///
#define LogNone(%message)         $$$LogEvent("NONE", %message)
///
#define LogError(%message)        $$$LogEvent("ERROR", %message)
///
#define LogFatal(%message)        $$$LogEvent("FATAL", %message)
Sean Connelly · Mar 28, 2017 go to post

Interesting idea, Haskell is certainly influencing other languages, so why not COS.

As an alternative to the op code...

1. Create an include file with...

#Define foreach(%c,%l) for i=1:1:%c.Size set %l=%c.GetAt(i) do

2. Then execute code as a semi anonymous function of foreach...

$$$foreach(newCollection,item).write !,item
Sean Connelly · Mar 28, 2017 go to post

I agree, the dot syntax is a bit old school.

At the moment its the only way that I can think of for passing code into the context of a map reduce function.

It doesn't look so bad when part of a wider COS code block...

ClassMethod Test2(){  set originalCollection = ##class(%ListOfDataTypes).%New()  do originalCollection.Insert("Sean")  do originalCollection.Insert("Mark")  do originalCollection.Insert("Bob")  $$$map(originalCollection,newCollection,item)  .$$$return($ZCONVERT(item,"U"))  $$$foreach(newCollection,item)  .write !,item}

If COS implemented Lambda syntax using arrow functions then it would look at lot cleaner.

It wouldn't be hard for the COS compiler to implement. The inner code block would be scoped of to its own underlying M function with its return value being a quit back to the output of the macro or classmethod call.

ClassMethod Test2(){  set originalCollection = ##class(%ListOfDataTypes).%New()  do originalCollection.Insert("Sean")  do originalCollection.Insert("Mark")  do originalCollection.Insert("Bob")  set newCollection=$$$map(originalCollection, (item) => {return$ZCONVERT(item,"U")
  })  $$$foreach(newCollection, (item) => {write !,item
  })

}
Sean Connelly · Mar 28, 2017 go to post

I was just thinking the same thing lol.

I spent 10 years waiting for a return statement and it passed me by without noticing.

Reminds me to read the release notes a bit more often!

Sean Connelly · Mar 28, 2017 go to post

> Let me be crystal clear and honest - this is horrible

LOL, well, lets crack open Ensemble and explore some macro code...

In all honesty, this post was not an advocacy but an exploration.

Map, Reduce and Filter are functions that I use every day in other languages that I never think to emulate in COS. Seeing the original OP it got me thinking, why can't we have it in COS as well.

It's good to explore these ideas, particularly as other languages are outpacing COS in a very big way. How else would they end up in the core language.

Sean Connelly · Mar 29, 2017 go to post

Good article, but not actually related.

MapReduce on big data is not the same as map, reduce and filter on small data collections.

Sean Connelly · Mar 29, 2017 go to post

Agreed Amir! ForEach would be a great start.

Or my preference for in...

for k1 in ^foo {
  for k2 in ^foo(k1) {
    //...
  }
}

A good precursor to having map, reduce and filter.

Sean Connelly · Apr 11, 2017 go to post

I think we need to see your source code.

If you are on 2016 then the parent code should not be transcompiled into your child class INT code unless there is some dynamic compile challenge to your code.

Sean Connelly · Apr 12, 2017 go to post

##SafeExpression will not work for you.

The method Abs() will get baked into A with the class name A.

B will no longer get its own baked method, so when Abs() is called on B, it will incorrectly return A.

Sean Connelly · Apr 12, 2017 go to post

Just wondering if you are missing a trick here.

If you are using try catch then your catch will return %Exception.AbstractException, this has a Location property.

Sean Connelly · Apr 12, 2017 go to post

Yes, excellent addition!

Rubber ducking is a great tool, and if no one is around then I will actually talk the problem out loud to myself. There is something about the vocal feedback loop to the brain that really helps. I've even asked my two dogs, to very curious looks lol.

Sean Connelly · Apr 13, 2017 go to post

Code reviews is a great suggestion. Having a style guide is a good supplement to this.

I was wondering if anyone had attempted a community driven style guide yet?

As an example, I have adopted this style guide for all of my JavaScript development...

https://github.com/airbnb/javascript

Sean Connelly · Apr 13, 2017 go to post

It does look interesting. I might find a few of the rules a little querulous, but there are a some gems in there, this ones priceless :)

Property of type %String without a MAXLEN

For what I want a 100 line linter would suffice for studio output. But I can see value in cachéquality if I was back managing a large team of developers again.

Sean Connelly · Apr 13, 2017 go to post

> QUIT from inside a loop is considered quitting a loop rather then a function, so it should always be without a value.

100% agreed.

My point was if the compiler will go as far as protecting the developer from this type of quit misshap, then could the compiler not also warn on potential quit <COMMAND> errors.

Sean Connelly · Apr 13, 2017 go to post

I'm still trying to get my head around this.

Are you saying that developers actively use $quit inside method code?

Sean Connelly · Apr 13, 2017 go to post

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
}
Sean Connelly · Apr 13, 2017 go to post

wow, that's interesting, never seen it used in methods that way, I guess its one way to work around the command error

so as a fringe case, if the lint tool found a $quit in a method then it would assume the developer knows what they are doing and ignore the whole method, that way no false postives

Sean Connelly · Apr 14, 2017 go to post

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?

Sean Connelly · Apr 15, 2017 go to post

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.

Sean Connelly · Apr 18, 2017 go to post

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.

Sean Connelly · Apr 18, 2017 go to post

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

Sean Connelly · Apr 19, 2017 go to post

I was trying to figure out if you had found a secret zip command on windows, but realised from your code you are using...

http://gnuwin32.sourceforge.net/packages/zip.htm

7zip has always been rock solid for me on windows, and is well maintained. The above zip lib looks like its almost 10 years old now?

Perhaps use a combination of both as per the HS.Util.Zip.Adapter class.

Sean Connelly · Apr 20, 2017 go to post

That error is caused by the extra curly brace, try...

SELECT JSON_OBJECT('id': '{}')

Sean Connelly · Apr 20, 2017 go to post

What happens if you run the same query without the JSON_OBJECT function...

SELECT idSQL,content FROM DocBook.block

Sean Connelly · Apr 20, 2017 go to post

Just adding a space will prevent the value being non escaped...

SELECT JSON_OBJECT('id': ' {{}')

Which you can add to the end as well...

SELECT JSON_OBJECT('id': '{{} ')

Which basically suggests that if a value starts with a { and ends with a } then the JSON_OBJECT function assumes that it is a valid JSON object and does not escape it, for instance this...

SELECT JSON_OBJECT('id': '{"hello":"world"}')

will output this...

{"id":{"hello":"world"}}

In some ways I would say this is valid / wanted behaviour, except perhaps that there should be a special Caché type for raw json where there is then an implicit handling of that type in these functions.

An alternative workaround that works is to use the CONCAT function to append a trailing space...

SELECT JSON_OBJECT('id':{fn CONCAT('{{}',' ')})

Which produces...

{"id":"{{} "}

Which on the original query would need to be...

SELECT JSON_OBJECT('idSQL':id, 'content': {fn CONCAT(content,' ')} ) FROM DocBook.block

Sean Connelly · Apr 20, 2017 go to post

I remember the encode / decode limitation was 3.6MB not 1MB, I corrected my original message.

Having built several EDT document solutions in Ensemble (sending thousands of documents a day) I have not had to code around this limitation.

But if you have documents that are bigger then take a look at GetFieldStreamBase64() on the orignal HL7 message. I've not used it, but should be fairly simple to figure out. In which case you can use an Ens.StreamContainer to move the message.

Thinking about it, there is an even simple solution, just send the HL7 message "as is" to the operation and do the extract and decode at the last second.

Sean Connelly · Apr 20, 2017 go to post

Just glancing comments.

You are trying to set a parameter. I'm no ZEN expert, but I am pretty sure parameters are immutable in all classes.

The other thing, if I was doing this in CSP. Setting the content-type in the OnPage method would be too late, the headers would already be written. It would have to be written before then. Not sure if ZEN is similar, but I would override the OnPreHTTP (or equivalent) and set %response.ContentType=myCONTENTTYPE in that method.