Question
· Jun 24, 2020

Is it possible to pass ClassMethods as arguments to another ClassMethod?

just wondered if there was a way to pass functions for execution to another function so that events can occur before and after?

like :

d ..MethodA(
   ..Method1("var"),
   ..Method2(0),
   ...
)

edit: 

I managed to get it working with execute but is there a cleaner (easier way)?

ClassMethod WriteSegment(Functions... As %String) As %Status
{
     w "Start",!
     f Fn=1:1:Functions {
          x Functions(Fn)
     }
     w "End",!
     Q $$$OK
}

ClassMethod WriteSomething(Arg As %String) As %Status
{
     w Arg,!
     Q $$$OK
}

ClassMethod Testing() As %Status
{
     d ..WriteSegment(
          "d ..WriteSomething(""Hi"")",
          "d ..WriteSomething(""Bye"")"
     )
     Q $$$OK
}
Discussion (16)3
Log in or sign up to continue

Not sure what you mean by occurring before and after.  However, you could pass the name of the method and then execute it using $ClassMethod within MethodA()

Something like:

Do ..MethodA("Method1","var","Method2",0)

MethodA {method1, arg1, method2, arg2) {

Do $classmethod($classname(),method1,arg1)

Do $classmethod($classname(),method2,arg2)

}

But again, I am not entirely sure what you want to accomplish here....

I think you could generalize the sample I pasted about which uses $classmethod but passing in a collection of some sort with the Method name as the first item and the arguments for the method as additional items and then iterate through that.  It could be a $LB of $LBs or a JSON object.  It would be easier if there was a set number of arguments that were allowed.  This approach is probably a little safer than the use of Exceute.

How about this then... couldn't find a better way to pass multiple args though...


ClassMethod WriteSegment(Functions As %DynamicArray) As %Status { w "Start",! s arrFns = Functions.%GetIterator() while arrFns.%GetNext(.Key, .Obj) { s Args="" s arrArgs = Obj.Arguments.%GetIterator() while arrArgs.%GetNext(.aKey, .Item) { s $LI(Args, *+1)=Item } d $Case($LL(Args), 0: $ClassMethod(Obj.Class, Obj.Method), 1: $ClassMethod(Obj.Class, Obj.Method, $LG(Args, 1)), 2: $ClassMethod(Obj.Class, Obj.Method, $LG(Args, 1), $LG(Args, 2)), 3: $ClassMethod(Obj.Class, Obj.Method, $LG(Args, 1), $LG(Args, 2), $LG(Args, 3)), 4: $ClassMethod(Obj.Class, Obj.Method, $LG(Args, 1), $LG(Args, 2), $LG(Args, 3), $LG(Args, 4)), 5: $ClassMethod(Obj.Class, Obj.Method, $LG(Args, 1), $LG(Args, 2), $LG(Args, 3), $LG(Args, 4), $LG(Args, 5)), 6: $ClassMethod(Obj.Class, Obj.Method, $LG(Args, 1), $LG(Args, 2), $LG(Args, 3), $LG(Args, 4), $LG(Args, 5), $LG(Args, 6)), 7: $ClassMethod(Obj.Class, Obj.Method, $LG(Args, 1), $LG(Args, 2), $LG(Args, 3), $LG(Args, 4), $LG(Args, 5), $LG(Args, 6), $LG(Args, 7)), 8: $ClassMethod(Obj.Class, Obj.Method, $LG(Args, 1), $LG(Args, 2), $LG(Args, 3), $LG(Args, 4), $LG(Args, 5), $LG(Args, 6), $LG(Args, 7), $LG(Args, 8)) ) } w "End",! q $$$OK } ClassMethod WriteSomething(Arg1 As %String) As %Status { w !,Arg1,! q $$$OK } ClassMethod WriteSomethingElse(Arg1 As %String, Arg2 As %String) As %Status { w !,Arg1,", ",Arg2,! q $$$OK } ClassMethod Testing() As %Status { d ..WriteSegment([ { "Class": ($ClassName()), "Method": "WriteSomething", "Arguments": [ "Hi" ] }, { "Class": ($ClassName()), "Method": "WriteSomethingElse", "Arguments": [ "Bye", "Waves" ] } ]) q $$$OK }

That may be the cleanest you can get it, unless you know that the methods are always Classmethods of the class you are calling in which case you don't need to send $classname() every time.  But sending it does allow for the generalized use-case of calling to different classes.

I was playing around to programmatically figure out how many argument a method accepts using %Library.ClassDefinition, however I just realized that if $classmethod() can't accept variable argument quantities than this doesn't help at all and you'd still end up with the approach that you have above.  

It may be worth a note to the WRC to ask for an enhancement to $classmethod() (unless someone chimes in here to say that there is a way to pass a variable number of arguments to it)

Its a shame theres no easier way i guess but ill stick with for now then unless anyone else has any alternatives.

I was going to make the Class property fall back to $this but then i decided to move this function to a utility class and call it within my WriteSegment method to make it more generic.

Unless theres a way to get the calling class within a method...?

You mean like this?

ClassMethod CallHook(hook As %String = "", args...) As %Status [ Private ]
{
  if hook = "" return $$$OK

  set classname = $piece(hook, ":", 1)
  set method = $piece(hook, ":", 2)
  try {
    return $classmethod(classname, method, args...)
  } catch ex {
    return ex.AsStatus()
  }
  return $$$OK
}

It certainly does for me.

https://github.com/rfns/frontier/blob/fe0868c8e0821ffdfd15407994288b2832...

This works both way.

Consider class:

Class dc.TestArgs
{

ClassMethod AcceptArgs(x...)
{
    write "Got following params",!
    zw x
}

ClassMethod NormalMethod(a As %String, b As %String, c As %String)
{
    write "we got in a: ",a,!
    write "we got in b: ",b,!
    write "we got in c: ",c,!
}

ClassMethod SendArgs()
{
    set p = 3
    set p(1) = "first parameter"
    set p(2) = "second"
    set p(3) = "third"
    do ..AcceptArgs(p...)

    write "works with usual argument style",!
    do ..NormalMethod(p...)
}

}

Notice in SendArgs we are constructing p -- array of arguments. We can pass it both to method that accepts args..., and to normal method.

USER>d ##class(dc.TestArgs).AcceptArgs(1,2,3)
Got following params
x=3
x(1)=1
x(2)=2
x(3)=3

USER>d ##class(dc.TestArgs).SendArgs()
Got following params
x=3
x(1)="first parameter"
x(2)="second"
x(3)="third"
works with usual argument stylewe got in a: first parameter
we got in b: second
we got in c: third

Well when i try that in the above code as:

ClassMethod WriteSegment(Functions As %DynamicArray) As %Status
{
    s arrFns = Functions.%GetIterator()

    while arrFns.%GetNext(.Key, .Obj) {
        s arrArgs = Obj.Arguments.%GetIterator()

        while arrArgs.%GetNext(.aKey, .Item) {
            s Args(aKey + 1)=Item
        }
        d $ClassMethod(Obj.Class, Obj.Method, Args...)

    q $$$OK
}

i get an error in the called function:
<UNDEFINED>zWriteSomething+1^Report.Elements.1 *Arg1
the Args array looks like this:
Args(1)="Hi"

Ahhhhhh, i didn't know that! Well in that case it works!
looks alot tidier now then:

ClassMethod ExecuteClassMethods(Functions As %DynamicArray) As %Status
{
    s arrFns = Functions.%GetIterator()

    while arrFns.%GetNext(.Key, .Obj) {

        s arrArgs = Obj.Arguments.%GetIterator()

        while arrArgs.%GetNext(.aKey, .Item) {
            s Args = aKey + 1
            s Args(Args) = Item
        }

        d $ClassMethod(Obj.Class, Obj.Method, Args...)
    }

    q $$$OK
}