Written by

Senior Startups and Community Programs Manager at InterSystems Corporation
Discussion Evgeny Shvarov · Apr 22, 2018

Class vs Routine in ObjectScript. What Do You Use And Why?

Hi, Community!

Have a question for general discussion. 

In ObjectScript we have cls for classes and mac code, which both compile into int code. 

Is there any reason when you use mac instead of cls  for non-persistent classes?

For me the benefits for cls are:

1. Inheritance and other OOP features

2. Auto-documented code 

For mac one visible benefit is easier call in terminal:

do method^Utils(p1,p2)

vs

do ##class(Package.Utils).method(p1,p2)

What is your choice and why?

Comments

Alice Shrestha · Apr 22, 2018

Another + for classes would be intellisense in studio. 

Although, I do like the public output variable list separation to input variable in mac files like so:

TEST (invar) [outvar] public {

}

0
Vitaliy Serdtsev  Apr 23, 2018 to Alice Shrestha

This can be done also in the CLS.

<FONT COLOR="#ff0000">TEST</FONT><FONT COLOR="#000000">(invar) [outvar] </FONT><FONT COLOR="#0000ff">public </FONT><FONT COLOR="#800080">{

}</FONT>

=>
<FONT COLOR="#000080">ClassMethod </FONT><FONT COLOR="#000000">TEST(</FONT><FONT COLOR="#ff00ff">invar</FONT><FONT COLOR="#000000">) [</FONT><FONT COLOR="#000080">PublicList</FONT><FONT COLOR="#000000">=outvar] {

}</FONT>

PublicList
0
Eduard Lebedyuk · Apr 22, 2018

Classes only.

Each method, that could be called from a terminal is documented with a sample call.

0
Robert Cemper · Apr 22, 2018

Classes and Methods forever!

#1) for documentation
#2) for all the possibilities and structural controls of  OO development.
 

#3)
.mac & .int is a left over from a previous millennium,
a (failing) attempt to mimic OO with the mindset of procedural methodology.

I'm personally disappointed that Atelier still supports mac.

It was a historical requirement. Accepted. For last millennium. Eventually still for some internals.
Definitely not for public use.

0
Dan Pasco  Apr 25, 2018 to Robert Cemper

I really enjoy fluent interfaces made possible by using classes and objects.

DEV>write {}.%Set("a","1").%Set("b",2).%Set("c","thought it would be 3!").%ToJSON()

{"a":"1","b":2,"c":"thought it would be 3!"}
0
Chris Stewart · Apr 23, 2018

If I'm writing something that will  maybe run 2 or 3 times, and is less than 10 lines of code, I might use a .mac.  For anything more than that, I would use a class, as the time saved in setup is pretty negligible compared to the rest of the work done

0
Akio Hashimoto · Apr 23, 2018

You should not be able to do it if you want to pass only compiled objects.

0
Akio Hashimoto  Apr 23, 2018 to Eduard Lebedyuk

I'm sorry.

Class only.

But, classes can not release OBJ only.

0
Fabian Haupt  Jul 12, 2018 to Sergei Shutov

and subsequently you can export compiled classes and only import deployed code on another system. So you never get source code on a customer's system.

0
Evgeny Shvarov  Jul 18, 2018 to Fabian Haupt

Hi, Fabian!

Could you share please the call to export the compiled classes without source code?

0
Jenna Makin · Apr 23, 2018

I think it really depends on your coding preferences. There are benefits to writing using Objects (CLS) and benefits to writing in (MAC) and which you use may simply depend on what your coding preferences are.
Do you like to code using objects, then CLS is the way to go. Are you a procedural programmer, then (MAC) is the way to go.

Using CLS, you program using objects with properties and methods. You gain the benefit of being able to access your object data via three different mechanisms, direct global access, object access, or sql. You implement methods and typically your methods are logically organized into the classes that they pertain to. You gain inheritance, XML support, JSON support, and so much more.

All that said, if you come from a background of procedural programming and don't have a need or desire to work in objects, there's nothing wrong with using MAC routines.

0
Dan Pasco  Apr 24, 2018 to Jenna Makin

I often see user classes that extend either %RegisteredObject or %Persistent that implement only class methods with no properties at all. This produces larger-than-needed runtimes.

For classes that implement only classmethods (static) there is no reason to inherit an instantiable interface such as that provided by %RegisteredObject.

It isn't necessary but it is good practice to define such classes to be abstract.

Using a simple helper class such as the one used by the various $system.<class> classes implemented by ISC it is possible to provide help at the command prompt for all methods implemented in the class.

I tend to lean toward classes-only but I do have requirements that can only be met by using routines. I'm not a fan of the ##class() syntax and, outside of instantiation, there aren't very good alternatives to that syntax. 

0
Evgeny Shvarov  Apr 24, 2018 to Dan Pasco

Oh, thanks, Dan! That's interesting.

So, practically speaking: 

Class My.Utils {

ClassMethod Foo {

///

}

}

Works faster than:

Class My.Utils Extends %RegisteredObject {

ClassMethod Foo {

///

}

}

Right?

0
Dan Pasco  Apr 24, 2018 to Eduard Lebedyuk

Not necessarily "faster" at execution time but the routine generated by compiling a class that unnecessarily extends %RegisteredObject or %Persistent will be much larger than the routine generated by a class that implements only static members and is abstract. That's less space consumed. The class descriptor/dispatch table will be smaller.

0
Evgeny Shvarov  Apr 24, 2018 to Dan Pasco

Thanks, Dan! 

That's helpful.

And from that class descriptor/dispatch table, can we say in general that call:

do foo^utils(p1) 

would be always faster than

do ##class(My.Utils).foo(p1) 

assuming that the code in foo() is same?

0
Dan Pasco  Apr 25, 2018 to Evgeny Shvarov

It seems logical that a class method call will be slightly slower than a direct routine function call but I'm not sure there is much difference. I've never conducted any benchmarks in a pure environment. It would be interesting for someone to test this.

0
Jenna Makin  Apr 25, 2018 to Dan Pasco

There are extra commands executed in order to call the class method, however the impact is negligible and the ultimate code executed is really identical assuming the method and routine code is the same 

Yes, Not extending %RegisteredObject will produce less code however the code you are executing will be identical.

0
Vitaliy Serdtsev · Apr 25, 2018

Still can be so:

<FONT COLOR="#000080">Class dc.test </FONT><FONT COLOR="#000000">[ </FONT><FONT COLOR="#000080">Abstract </FONT><FONT COLOR="#000000">]
{

</FONT><FONT COLOR="#000080">ClassMethod </FONT><FONT COLOR="#000000">Test(</FONT><FONT COLOR="#ff00ff">s </FONT><FONT COLOR="#000000">= </FONT><FONT COLOR="#800080">"#"</FONT><FONT COLOR="#000000">) {   </FONT><FONT COLOR="#0000ff">w </FONT><FONT COLOR="#008000">"Test_"</FONT><FONT COLOR="#000000">,</FONT><FONT COLOR="#800000">s </FONT><FONT COLOR="#000000">}

</FONT><FONT COLOR="#000080">ClassMethod </FONT><FONT COLOR="#000000">mac() [ </FONT><FONT COLOR="#000080">ProcedureBlock </FONT><FONT COLOR="#000000">= 0 ] { </FONT><FONT COLOR="#ff0000">sub1</FONT><FONT COLOR="#000000">(s=1)   </FONT><FONT COLOR="#0000ff">w </FONT><FONT COLOR="#008000">"sub1_"</FONT><FONT COLOR="#000000">,</FONT><FONT COLOR="#800000">s   </FONT><FONT COLOR="#0000ff">q </FONT><FONT COLOR="#ff0000">sub2</FONT><FONT COLOR="#000000">(s=2)   </FONT><FONT COLOR="#0000ff">w </FONT><FONT COLOR="#008000">"sub2_"</FONT><FONT COLOR="#000000">,</FONT><FONT COLOR="#800000">s   </FONT><FONT COLOR="#0000ff">q </FONT><FONT COLOR="#ff0000">procPrivate</FONT><FONT COLOR="#000000">(s=3) </FONT><FONT COLOR="#800080">{   </FONT><FONT COLOR="#0000ff">w </FONT><FONT COLOR="#008000">"procPrivate_"</FONT><FONT COLOR="#000000">,</FONT><FONT COLOR="#800000">s </FONT><FONT COLOR="#800080">} </FONT><FONT COLOR="#ff0000">procPublic</FONT><FONT COLOR="#000000">(s=3) </FONT><FONT COLOR="#0000ff">public </FONT><FONT COLOR="#800080">{   </FONT><FONT COLOR="#0000ff">w </FONT><FONT COLOR="#008000">"procPublic_"</FONT><FONT COLOR="#000000">,</FONT><FONT COLOR="#800000">s </FONT><FONT COLOR="#800080">} </FONT><FONT COLOR="#000000">}

}</FONT>

Result:
USER><FONT COLOR="#0000ff">d </FONT><FONT COLOR="#ff0000">zTest</FONT><FONT COLOR="#000000">^dc.test.1(1)</FONT>
Test_1
USER><FONT COLOR="#0000ff">d </FONT><FONT COLOR="#ff0000">sub1</FONT><FONT COLOR="#000000">^dc.test.1</FONT>
sub1_1
USER><FONT COLOR="#0000ff">d </FONT><FONT COLOR="#ff0000">sub2</FONT><FONT COLOR="#000000">^dc.test.1</FONT>
sub2_2
USER><FONT COLOR="#0000ff">d </FONT><FONT COLOR="#ff0000">sub1</FONT><FONT COLOR="#000000">^dc.test.1(10)</FONT>
sub1_10
USER><FONT COLOR="#0000ff">d </FONT><FONT COLOR="#ff0000">sub2</FONT><FONT COLOR="#000000">^dc.test.1(10)</FONT>
sub2_10
USER><FONT COLOR="#0000ff">d </FONT><FONT COLOR="#ff0000">procPrivate</FONT><FONT COLOR="#000000">^dc.test.1(10)</FONT>

D procPrivate^dc.test.1(10) ^ <NOLINE> USER><FONT COLOR="#0000ff">d </FONT><FONT COLOR="#ff0000">procPublic</FONT><FONT COLOR="#000000">^dc.test.1(10)</FONT> procPublic_10 USER>

0
Dan Pasco  Apr 25, 2018 to Vitaliy Serdtsev

Yes, that is true today but dispatching directly to labels in a class runtime is very fragile.

0
Evgeny Shvarov  Apr 25, 2018 to Vitaliy Serdtsev

True! Nice hack ;) Thank you, Vitaly!

But obviously, you cannot use this calls in a code, because dc.test.1 can be dc.test.2 etc...

0
Dmitry Maslennikov  Apr 25, 2018 to Vitaliy Serdtsev
USER>zload dc.test.1
 
USER>do sub1()
sub1_1
USER>do sub2()
sub2_2
USER>do sub2(10)
sub2_10
USER>do procPrivate(10)
 
DO procPrivate(10)
^
<NOLINE>^dc.test.1
USER>do procPublic(10)
procPublic_10
USER>do sub1
sub1_1
0
Jenna Makin · Apr 25, 2018

Another reason to opt for Objects over Routines is that Objects provide an automated documentation mechanism that Routines dont.  All elements of a class are documented in the class documentation and the developer can add their own text documentation as well

0
Ben Spead · Apr 25, 2018

Also, Classes lend themselves much better to programmatic access and manipulation of their content (you can do this with routines too but it is harder to do so due to their unstructured nature).

For example, the server-side Source Control hooks we use can programmatically insert an RCS Keyword as a class parameter ("SrcVer") into any class which doesn't already have it defined.   This is extremely powerful because it allows source control to create an automatic 'watermark' in every class created for our internal applications which can be programmatically access or from terminal:

SSO>write ##class(AppS.WebClient).#SrcVer
$Id: //custom_ccrs/us/ISCX/SSO/BASE/cls/AppS/WebClient.xml#6 $

This would require much more plumbing to do automatically with a routine!

0
Eugene Karataev · May 1, 2018

Routines are preferable if need to call routine from the other namespace.

0
Evgeny Shvarov  May 1, 2018 to Eugene Karataev

Why? Can't class methods be called from another namespace? Or it is easier with routine?

0
Jenna Makin  May 2, 2018 to Evgeny Shvarov

Im pretty sure that class methods cant be called in another namespace without wrapping the method call in a call that actually switches namespaces, calls them method and then switches back.

do ["namespace"]obj.method 

isn't supported where

do ["namespace"]tag+offset^routine

is

0
Nicki Vallentgoed  May 2, 2018 to Jenna Makin

Just map the class from namespace A to B and you can use it.

0
Jenna Makin  May 2, 2018 to Nicki Vallentgoed

Yes, that is true, however, that doesnt allow you to execute a class method from namespace A in namespace B.

When you :

do ["b"]tag^routine

Cache actually executes tag^routine in namespace b vs the namespace you are currently in.

Creating a package map for namespace a that maps a particular package from namespace b only makes that class available to namespace a.  When I execute methods within the mapped class those methods are executed in namespace a and not b.

0
Nicki Vallentgoed  May 3, 2018 to Jenna Makin

I might be misunderstanding you, but I do not agree.

Class is in A and mapped to B.

ClassMethod t1() As %Status
{
w $NAMESPACE,!
q 1
}

A>d ##class(Test.PopNicki).t1()
A

A>zn "B"

B>d ##class(Test.PopNicki).t1()
B

B>
 

0
John Murray  May 3, 2018 to Jenna Makin

Kenneth, I don't think you are correct here. Apart from invalid syntax you gave, even with the correct syntax the routine is fetched from the specified namespace and executed in the current namespace. See the following example using a simple test routine (YJM) that I created in my USER namespace and then executed from my SAMPLES namespace:

USER>zl YJM zp
YJM      w !,"Runs in ",$namespace,! q
 

USER>d ^YJM

Runs in USER

USER>zn "SAMPLES"

SAMPLES>d ^YJM

D ^YJM
^
<NOROUTINE>^YJM *YJM
SAMPLES>d ^|"USER"|YJM

Runs in SAMPLES

SAMPLES>w $zv
Cache for Windows (x86-64) 2017.2.1 (Build 801U) Wed Dec 6 2017 09:07:51 EST
SAMPLES>d ["USER"]YJM^YJM

D ["USER"]YJM^YJM
^
<SYNTAX>^YJM
SAMPLES>

0
Jenna Makin  May 3, 2018 to John Murray

Interesting, I dont see anywhere in the docs where it would indicate that D ["SAMPLES"]YJM^YJM will work

The docs would indicate that you can do this:

DO ^["SAMPLES"]Y2K

However, that throws a SYNTX error too

0
John Murray  May 3, 2018 to Jenna Makin

I didn't expect D ["SAMPLES"]YJM^YJM to work, but that's the syntax you used in your earlier comment. Probably a typo.

I agree, it's interesting that the older-style square-bracket extended reference syntax doesn't work in this context. You have to use the newer-style vertical-bar syntax.

0
Jenna Makin  May 3, 2018 to John Murray

Okay, looked at the docs more and my eyes were playing tricks on me.

Looks like you cant call a routine at a tag in another namespace, but can call a routine at the top in another namespace.

The syntax is:

do ^|"namespace"|routine

so:

do ^|"SAMPLES"|YJM

0
John Murray  May 3, 2018 to Jenna Makin

I disagree with your assertion that you can't call at a tag in a way that you can call from the top:

USER>zp
YJM      w !,"Runs in ",$namespace,! q
         ;
SUB      ; A subroutine tag
         w !,"SUB^YJM runs in ",$namespace,!
         q
 
 
USER>zn "samples"
 
SAMPLES>d SUB^|"USER"|YJM
 
SUB^YJM runs in SAMPLES
 
SAMPLES>

I find it confusing that you talk about calling a routine in a namespace. As I see it you're fetching it from another namespace (i.e. the one the routine lives it), but you're running it in your current namespace.

You also need to be aware of what happens if the code you fetch from the other namespace makes its own calls to other code. Here's an illustration:

USER>zp
YJM      w !,"Runs in ",$namespace,! q
         ;
SUB      ; A subroutine tag
         w !,"SUB^YJM runs in ",$namespace,!
         q
         ;
Test1    ;
         w !,"About to call local line label YJM",!
         d YJM
         q
         ;
Test2    ;
         w !,"About to call a line label in a specific routine",!
         d SUB^YJM
         q
 
USER>d Test1^YJM
 
About to call local line label YJM
 
Runs in USER
 
USER>d Test2^YJM
 
About to call a line label in a specific routine
 
SUB^YJM runs in USER
 
USER>zn "SAMPLES"
 
SAMPLES>d Test1^|"USER"|YJM
 
About to call local line label YJM
 
Runs in SAMPLES
 
SAMPLES>d Test2^|"USER"|YJM
 
About to call a line label in a specific routine
 
 d SUB^YJM
 ^
<NOROUTINE>Test2+2^YJM *YJM
SAMPLES 2d0>
 
SAMPLES 2d0>q
 
SAMPLES>

0
Robert Cemper  May 3, 2018 to John Murray

if ever this should be of practical use
then I'd suggest to move such a "everybody's darling" routine to %SYS and name it %zYJM

0
Evgeny Shvarov  May 3, 2018 to Robert Cemper

Or map it into %ALL namespace

0
Nicki Vallentgoed · May 2, 2018

Classes all the way.

I have yet to see a compelling argument to use routines the later versions of cache.

0
Stephen Wilson · May 3, 2018

In practice, the majority of artefacts in our Caché instances are .int files.  We have used classes for mapping globals, .NET web development and writing stored procedures. It is nice to be able to call a classmethod from within a .NET application using a tool-generated proxy class. I detest code that jumps all over the place and Xecute statements within globals. Please stop coding this madness! Let's make the world a better place...

I confess some of the .NET software we have calls a classmethod which in turn calls a legacy .int routine with a Do statement and many subsequent Do statements. Not ideal but necessary if you want to avoid re-writing everything on a massive system. You can also use ClassExplorer with your classes and the "comment-style" self-documentation for classes is useful.  Much of the code we support uses the legacy line-based syntax and there is truly no replacement for well-structured, easy-to-read code. It just makes debugging less of a headache. I love line-spacing and curly braces - the simple things you take for granted in other programming languages. I also can't see anyone doing RESTful API development without classes.

Having human-readable, intuitively named class packages rather than hundreds of obscurely named .int routines and globals makes life so much easier. Having a class in a package in a BIO namespace called BIO.Request with CRUD methods reads much better than an interactive programme invoked by do ^REQ. I also like being able to export self-contained class packages rather than trying to work out what .int files and globals I need to export from memory.

0
Ponnumani Gurusamy · Jun 22, 2018

I used both of routine and cls. But I did like for cls . We are appliying FHIR concept to class easily and  generated the structure of the data flow.

0