Hi. I've never come across any automatic conversion from character user interface (CHUI) to any web-based one. You might stand a chance if the screens were written in some sort of "screen generator" framework, but most I've seen are basically hand-coded, and the problem is that they work on a field-by-field basis. So they read one field, validate input, update bits of the screen and data, and then move to the next field. This does not match well with web pages that tend to work a screen at a time, so automatic conversion is impossible. Sorry about that. I'd like to be proved wrong, of course - maybe some of the systems I work on could be converted! Anyone?

The reason that there is no sort "function", is that sorting is part of the basic storage structure that the whole system is built on. Arrays, both global and local, are automatically sorted as you set them up (effectively by an insertion sort). No need for a separate program or request. Just set up the data in an array as you go along and it will be sorted when you need it. This has always been a basic idea in the language, right from the original MUMPS, so the documentation may well skip over it a bit. However, when you get used to it, it works well.

Hi,

I recently needed a temporary class (not just a table) to store data while it was manipulated (imported, queried, modified, etc. and eventually exported) . I eventually set up all the storage locations with PPG refs as noted above, which works, but did wonder if there was a class parameter I had missed that just indicated the extent was temporary. Or maybe an alternative to extending %Persistent?

Mike

Hi Stuart,

As others have said, it's best practice to build a new variable, rather than amending what you have (there's some fancy name for the rule, I think, or maybe just "functional programming").

Looking at the string, I assume it is HealthShare HL7 message format, so the contents are pretty limited. In which case maybe a shortcut could be used:

s out=$Replace($Replace(list,"~]",""),"~[","")

Here's another alternative:

w $ZSTRIP($ZSTRIP(list,"*","[]"),"=>P")

I admit it could go horribly wrong with multiple nesting (I don't remember all the possible formats), so needs some testing. 

Hope you're keeping well,

Mike

Sadly, in my team we've all been writing MUMPS for so long that the abbreviated style comes naturally and is a hard habit to break. Yes, expanded is better for new starters in the language.

However... Playing devil's advocate you could say that abbreviated commands are:

1. Faster to type (as you said).

2. More compact. Allowing the reader to "see" more of the structure in one go.

You see, you can expand things out too much, in my opinion. Also, it only takes a few minutes for a (reasonable) programmer to get that "S" means "set", "I" means "if", etc. Commands always appear in the same part of the code (unlike some languages), there are not that many to learn, and once you know them, you can read them! So why bother with extra letters? After all "set" is itself only a token for "put the value on the right of the = into the variable on the left" or something like that. It could be "make" or "update" or "<-" (look up the programming language "APL" on Wikipedia if you want a real scare).

I think the main problem with "old fashioned" code is usually poor label/variable names, squeezing too much on one line, and lack of indenting. It's hard to read mainly because of the other parts of the code, not because the commands are single characters. Some things should be longer to better convey what they are for (though not as long as COBOL), and more lines can help convey program structure.

While I'm here, I'm not that keen on spurious spaces in "set x = 1", as opposed to "set x=1". It just spreads out the important stuff  - spaces are there to split out the commands.  :-) 

Mike

Hi. It depends on what you mean by "certain criteria". If it's a special file name then you could amend the FileSpec  property to skip the ones you don't want yet. If it's in the content, then maybe you should be reading in the file (creating a copy or allowing archive so the original continues to exist) and sending it as a message into Ensemble that can then  be held up in a business process until ready to send out to an Operation that creates an output file. That is the way ensemble is supposed to work, so you get a full record of what happened, etc.

(Otherwise, I'm pretty certain that there are actions that reset the list of processed files - maybe resetting that file path or restarting the job - but I cannot find the documentation about it at the moment. )

Hi.

Recently, well yesterday, I needed to do exactly the same , and on a class property as well! I found an answer more by accident than design:

s data=$LB(1,2,3)

s data=$LI(data,1,*-1)

zw data
data=$lb(1,2)

When I saw the other answer, I worried that this might not work when the result is only one item, but it does, as confirmed by the documentation for the $LIST function. If you supply all three parameters - list, position, end - then it always returns another list. I was pleasantly surprised!

Mike

Hi / It sounds like a good idea! I can think of a number of interfaces I've seen where the target application - a small local system - struggled to keep up with the flow of updates from a large PAS.

The only thing I've done like it was complicated, and had to use a proper Business Process. In that case the "department" was neonatal, so we were only interested in patients admitted to a particular ward. The solution looked for HL7 admissions and transfers to that ward, and when found used the data to create a local record in Caché. Then all other types of message could be checked against those records to see if it needed further processing and passing on (to "BadgerNet" eventually when a full episode was built up). Of course this only works if you can define a clear "starting point" that can be spotted in the message stream.                        / Mike

Hi. The "clean code" people would recommend just one parameter max, and better would be none! But I think that's going a bit far, and agree that 3, or maybe 4, maximum should be aimed for to keep things easy to understand when reading, though there may be exceptions.

The array passing is a good idea to reduce the number for normal routines, but I think the ideal for classes is using objects. If you are truly embracing objects, and self-documenting code, then new classes are usually needed and what used to be parameters become the setting of properties, like this:

table=##class(CMT.UI.Table).%New()
table.TopLine=10,table.BottomLine=21
table.HeaderFormat="Underline"
table.DefineQuery("CMT.UI.PatchSite:MyList")
table.AddColumn(3,,"BOLD")
...etc.

table.Display()

It works well in some cases, but I have to admit that there is a tendency for the number of classes to get a bit silly if you take it to the extreme. Sometimes simple code is best.  :-)     / Mike

I think that is my preferred method as well, but it depends to some extent what you are going to do with the result, and what you want to happen if the input number is too big. This $J solution will always return all the characters input, which may be the safest thing. (Though any space characters inside the input will get converted to zeros.)

When the fail mode needs to still return the same length string, e.g. to avoid messing up some fixed length message format, it might be best to use $E(), e.g.

W $E("0000"_number,$L(a)+1,*)

I've also seen the following used, but I'm not sure I recommend it. So many interesting ways it could go wrong!

w $E(number+10000,2,999)

Hi. I was actually looking up some information about pattern matching, but came across this warning:

If a call attempts to use indirection to get or set the value of object properties, it may result in an error. Do not use calls of this kind, as they attempt to bypass property accessor methods (<PropertyName>Get and <PropertyName>Set). Instead, use the $CLASSMETHOD, $METHOD, and $PROPERTY) functions, which are designed for this purpose

This was from https://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=GCOS_operators#GCOS_operators_pattern

So it looks like it may well work now, but there's no guarantee it will always work.

Surely there is a $method() call you can use to get the next item in the array?

Hi, I also help support an NHS trust using Ensemble, and it also has ever-growing PDF files in messages. We have our incoming PDFs as external file streams and it helps, though you have to bear in mind that the files are not going to be part of the cache backup for Disaster Recovery, etc. (Not sure about mirroring. I'd assume they don't get mirrored either as the contents are not in the journal.)

As yet, we don't have as big a problem as you - less messages and we only keep 92 days - but that is just as well as the PDF files are converted to base64 encoded in HL7 v2 messages, so they then do take up space in the database, and the journal, and the backup, which has resulted in the need to expand the disk space recently. I can recommend keeping Ensemble on a virtual server with disk expansion on demand.

I tend to think the problem is not going to go away whatever you do. I assume, like us, the PDFs come from 3rd party applications and they are always going to be producing  ever more and prettier documents as time goes by. So I recommend looking at more disk. :-)  / Mike

Hi,

I won't claim this is an answer, because it's not quite the same and people may object to the structure, but here is one solution that is used quite a lot in code I look after. Basically, a subroutine is called and then tests are done and a Quit is used to drop out when a match is found. Often used for validation, something like this that returns a result in the zER variable:

V1 ; Validate ORGC
   S ORG=zORG WC2^hZUTV zER'="" Q
   I IPACC<9,'$D(^hIW(WAID)) zER="No details set up for ward" Q
   D WARDON^hILO1 LOCK zER="Ward in use" Q

VQ2 Q

Apologies for the old-fashioned code! However, you can see each test can be quite complex and using lots of variables, but it is easy to understand as long as you expect the structure to work that way.

This is very similar to the "clean code" solution of making the whole thing into a function that returns a value:

ClassMethod Main(val1 As %String, val2 As %String)
 {
  write ..MyOutput(val1,val2)
 }

ClassMethod MyOutput(val1 As %String, val2 As %String) As %String
 {
  if val1 = 1 return "case 1"
  if val1 = 2, val2="*" return "case 2"
  return "default match"
 }

Of course there are probably as many answers as there are Cache programmers!  :-)

Yes, we have something like that, except we use the letter "q" as the prefix, and we follow it by the programmer's initials so that it becomes a "personal" set that is left alone in all namespaces, dev-test and live. We also extend this to rule to globals and things inside the application like functions, screens, tasks, etc. The in-house configuration management system we use ignores them so they are left untouched. It's a useful convention.

(We might have used "z" like you, but it was already taken for "utility/library" stuff.)

Neither - I think it best to just remove all the code and leave a stub with just a comment (usually with the change request id and reason).

That ensures the unused code is removed from all downstream libraries, so does not pop up in searches and testing, and yet keeps a record of its previous existence. I've always been against leaving unused code in source files, even if it's "commented out".

An alternative solution that works for us is to use the "Schedule" setting to run it for 30m (to allow some leeway as the job takes a while), and then set the "Call Interval" setting to something very large like "999999". This is for an Inbound SQL adaptor. (If something goes wrong with this overnight run then we manually remove the "Schedule" setting and restart the Service. Once complete, we put back the setting ready for the next night.)