Question Daniel McGowan · Jun 24, 2020

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

Go to the original post@Daniel McGowan

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
}

Comments

Ben Spead · Jun 24, 2020

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....

0
Daniel McGowan  Jun 24, 2020 to Ben Spead

Thanks for the response, just updated my OP, maybe its clearer now what i am trying to acheive...

0
Ben Spead  Jun 24, 2020 to Daniel McGowan

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.

0
Daniel McGowan  Jun 24, 2020 to Ben Spead

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
}
0
Daniel McGowan  Jun 24, 2020 to Marc Mundt

The classmethod function doesnt seem to accept them using .Args or Args... syntax

0
Ben Spead  Jun 24, 2020 to Daniel McGowan

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)

0
Daniel McGowan  Jun 24, 2020 to Ben Spead

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...?

0
Daniel McGowan  Jun 25, 2020 to Rubens Silva

I think thats because you are passing the arguments into the CallHook function formally, which i am not here. I tried constructing an array in the arrArgs loop and passing that byRef but it didnt work...

D

0
Alexander Koblov  Jun 25, 2020 to Daniel McGowan

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
0
Daniel McGowan  Jun 25, 2020 to Alexander Koblov

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"

0
Alexander Koblov  Jun 25, 2020 to Daniel McGowan

You need to specify number of parameter in the variable:

s Args=10

See my original example

0
Daniel McGowan  Jun 25, 2020 to Alexander Koblov

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
}
0
Eduard Lebedyuk  Jun 25, 2020 to Daniel McGowan

You can replace

s Args = aKey + 1s Args(Args) = Item

with:

s Args($i(Args)) = Item
0