Announcement

Collapse
No announcement yet.
X
  • Filter
  • Time
  • Show
Clear All
new posts

  • passing a class instance between Stata and Mata

    Hello,

    I have a need to pass an instance of a class created in Mata to Stata and then get it back from Stata. I tried different approaches but each was blocked by one Mata limitation or another:

    1. The most straightforward solution is to return a transmorphic scalar/matrix (or a pointer to it) to Stata. But st_numscalar, st_local etc only accept a real or string parameter, and there is no way that I could find to convert between transmorphic scalar/matrix (or a pointer to it) and real or string scalar.

    2. Another solution is to hold the instances in an asarray/vector in Mata and pass to Stata a handle (real scalar) that when passed back by Stata can be used to retrieve the corresponding instance from the asarray/vector. The obstacle here is that the asarray/vector must persist between function calls, and I could not find a way to create a global Mata variable (AFAIK, Mata does not support declaring variables outside functions) or the equivalent of C static functions.

    3. Also tried to create a class with a public static transmorphic scalar as a member and a static accessor function, but any assignment to the static member in the static function results in error 3000 invalid lval.

    Does anyone know a way around or possibly a totally different solution.

    Thanks,
    Last edited by Salah Mahmud; 29 Oct 2017, 01:57.

  • #2
    Yes, you can have variables defined outside a function and they persist in Mata even if you leave it and come back. Here's a simple example which I hope is relevant to your needs:
    // In Stata
    Code:
    mata mata clear
    mata:
    X = 999
    function myfcn (
        real scalar whatever)
    {
       external real scalar X
       printf("Hello world.  My global X = %5.0f\n", X)
    }
    myfcn(X)
    end
    display "Now we're back in Stata."
    mata: printf("Persisting across calls to Mata, my global X still = %5.0f\n", X)
    See the entry on "external variable" in [M-6] Glossary.

    Comment


    • #3
      Hi Mike,
      Of course you are right.
      What threw me off is that I kept getting an error if I tried to declare a variable outside a Mata function (something vague like “something found when nothing was expected!!”).
      <rant> It is unexpected behaviour to be able to initialize a variable before you declare it (when matastrict is on), but that is exactly the syntax required for using ‘external’ variables. Not to mention that the label ‘external’ is misleading (at least in the C sense of extern which means that the variable/function is defined elsewhere e.g., in an external library). A variable defined in the same file is hardly external </rant>.

      Thanks for the insights, Mike,

      /salah

      Comment


      • #4
        You don't need a static member function in order to accomplish what you want. You can do it with just a static member variable. See below.

        The illustration has a probe class ("KeepMe") that is instantiated and then set with a tell-tale message in order to distinguish it as the instantiated object later. After it is instantiated and ID'd, it is stored in an instantiated object of a storage class ("KeepEm") that keeps the probe object (or rather a pointer to it) in a static member variable that is set and accessed via conventional (not static) member functions. This avoids the problem that you were experiencing with static member functions, and yet is able to retain an instantiated object through a journey from Mata to Stata and back.

        This approach will accomplish your objective without resorting to global variables and their problems.

        In the illustration below, after the sojourn to Stata and back, the original instantiated object is retrieved from another instantiated object of the storage class (the probe object is still there, because the member variable is static) via a conventional (not static) accessor function, and then it's tested to prove that it's the original object that was instantiated before the trip to Stata and back.

        Code:
        version 15.0
        
        clear *
        
        mata:
        mata set matastrict on
        
        class KeepMe {
            private:
                string scalar message
            public:
                final void setMessage()
                final void reportMessage()
        }
        void function KeepMe::setMessage(string scalar message) this.message = message
        void function KeepMe::reportMessage() printf(message + "\n")
        
        class KeepEm {
            private:
                static pointer(class KeepMe scalar) scalar keepme_pointer
            public:
                final void setKeepMe()
                final class KeepMe scalar getKeepMe()
        }
        void function KeepEm::setKeepMe(class KeepMe scalar keepme) keepme_pointer = &keepme
        class KeepMe scalar function KeepEm::getKeepMe() return(*keepme_pointer)
        
        void function createm() {
            class KeepMe scalar keepme
            keepme.setMessage("Hello, world!")
        
            class KeepEm scalar keepem
            keepem.setKeepMe(keepme)
        }
        
        void function testem() {
            class KeepMe scalar keepme
            keepme.setMessage("You should see 'Hello, world!' alone if you get back " +
                "the original instantiated object and not this message.")
        
            class KeepEm scalar keepem
            keepme = keepem.getKeepMe()
        
            // Here's the proof:
            keepme.reportMessage()
        }
        
        // Set up and exit to Stata
        createm()
        
        end
        
        // Now in Stata; now back to Mata for the test
        
        mata:
        mata set matastrict on
        
        testem()
        
        end
        
        exit
        Note that it's important that the data type of the storage member variable be a pointer to the class and not the class, itself.
        Code:
        version 15.0
        
        clear *
        
        mata:
        mata set matastrict on
        
        class A {
        }
        
        class BPointer {
            private:
                pointer(class A scalar) scalar a
            public:
                final void setA()
        }
        void function BPointer::setA(class A scalar a) this.a = &a
        
        class BClassItself {
            private:
                class A scalar a
            public:
                final void setA()
        }
        void function BClassItself::setA(class A scalar a) this.a = a
        
        a = A()
        b = BPointer()
        b.setA(a)
        
        b = BClassItself()
        b.setA(a)
        
        end
        
        exit

        Comment


        • #5
          Joseph Coveney
          I have greatly benefited from your replies to questions related to class programming. In the above code, you have shown great dexterity. For my understanding, can you comment on how the following approach is different from what you have suggested. I am interested in knowing how your approach delivers more utility. This is what I normally do.
          1. Instantiate an object inside Stata program
          2. Set its elements through getter functions inside Stata
          3. Pass the instantiated object from Stata to Mata using a function
          4. Use the class attributes / members inside Mata
          Regards
          --------------------------------------------------
          Attaullah Shah, PhD.
          Professor of Finance, Institute of Management Sciences Peshawar, Pakistan
          FinTechProfessor.com
          https://asdocx.com
          Check out my asdoc program, which sends outputs to MS Word.
          For more flexibility, consider using asdocx which can send Stata outputs to MS Word, Excel, LaTeX, or HTML.

          Comment


          • #6
            Looking at Joseph Coveney's first set of code I find something very troubling. The function createm() is called first, and creates a new instance of the KeepEm and names it keepem, to then set the pointer to the object that holds the message. In the function testem() he, once again, creates a new instance of the KeepEm and, again, names it keepem. At this point the old instance should have been wiped out of memory, and the new instance should have an empty pointer in keepme_pointer, but it doesn't. I find this extremely troubling, because I strongly believe that the second declaration of the object with the same name should completely wipe out the first instance of the object.
            Alfonso Sanchez-Penalver

            Comment


            • #7
              Originally posted by Alfonso Sánchez-Peñalver View Post
              At this point the old instance should have been wiped out of memory, and the new instance should have an empty pointer in keepme_pointer, but it doesn't. I find this extremely troubling, because I strongly believe that the second declaration of the object with the same name should completely wipe out the first instance of the object.
              I think that there's a couple of things going on here.

              First, yes, the name keepem is the same for the two objects, but the two usages of the same name or token are not within the same scope. They're in separate functions, and so despite incidentally sharing the same name or token, the objects are separate and won't wipe each other out.

              Second, the pointer member variable is declared static and so all contemporary instances of the class KeepEm will share the same pointer variable pointing to the same instance of KeepMe.

              Comment


              • #8
                Originally posted by Attaullah Shah View Post
                This is what I normally do.
                1. Instantiate an object inside Stata program
                2. Set its elements through getter functions inside Stata . . .
                I'm not sure how you do that. There is a second object-oriented programming facility that's actually the first, i.e., long before Mata's.
                Code:
                help classman
                But I think that its objects are not interchangeable with those created in Mata.

                So, I'm a bit confused as to what you're saying. Are you saying that you create a Mata object in Mata code inside an ado-file?

                Comment


                • #9
                  Joseph Coveney
                  This is how I do it.

                  Code:
                  prog testclass
                  syntax [anything], [font(str)]
                  
                      mata : mft = FormatClassAsdocx()
                      mata : mft.font_family = "`font'"
                  
                      mata : displaydet(mft)
                  
                  end
                  
                  mata
                  class FormatClassAsdocx {
                  
                          string scalar font_family
                  }
                  void displaydet(class FormatClassAsdocx scalar mft){
                      "You have use the " + mft.font_family + " Font"
                  }
                  end
                  To test it
                  Code:
                   testclass, font(Verdana)
                    You have use the Verdana Font
                  To rephrase, my question is how the use of pointers can deliver more utility in this context. Second, if someone / program clears the mata memory, will the pointers still exist and retrieve the stored information. If not, then how can one protect against clearing mata memory?

                  If there is no way one can guard against this, are we left with the only option of using globals or there can be other efficient ways to store session information.
                  Last edited by Attaullah Shah; 29 Nov 2020, 02:42.
                  Regards
                  --------------------------------------------------
                  Attaullah Shah, PhD.
                  Professor of Finance, Institute of Management Sciences Peshawar, Pakistan
                  FinTechProfessor.com
                  https://asdocx.com
                  Check out my asdoc program, which sends outputs to MS Word.
                  For more flexibility, consider using asdocx which can send Stata outputs to MS Word, Excel, LaTeX, or HTML.

                  Comment


                  • #10
                    Originally posted by Attaullah Shah View Post
                    To rephrase, my question is how the use of pointers can deliver more utility in this context.
                    I don't know about your question. I used pointers in order to address the OP's question, which is different from yours. The OP was interested in going from Mata to Stata and then back again. Yours seems to be a standard ado-file with a standard Mata component. I think that such a conventional Stata-Mata mixed application doesn't often require pointers.

                    Second, if someone / program clears the mata memory, will the pointers still exist and retrieve the stored information.
                    I assume that clearing Mata's memory will clear Mata's memory, including pointers, although I haven't specifically looked into it.

                    If not, then how can one protect against clearing mata memory?

                    If there is no way one can guard against this, are we left with the only option of using globals
                    Again, I assume that deliberately clearing Mata's memory is not intended or designed to be protected against, and that clearing Mata's memory will clear Mata's memory, including Mata's global variables. So, I'm not sure that global variables are an option here.

                    Comment


                    • #11
                      Originally posted by Joseph Coveney View Post
                      I think that there's a couple of things going on here.
                      I've illustrated both phenomena below: same object name in different scope of execution & sharing pointed-to object in static member variable between two instances of storage object.

                      .ÿ
                      .ÿversionÿ16.1

                      .ÿ
                      .ÿclearÿ*

                      .ÿ
                      .ÿlocalÿline_sizeÿ`c(linesize)'

                      .ÿsetÿlinesizeÿ80

                      .ÿ
                      .ÿmata:
                      -------------------------------------------------ÿmataÿ(typeÿendÿtoÿexit)ÿ------
                      :ÿmataÿsetÿmatastrictÿon

                      :ÿ
                      :ÿclassÿProbeÿ{
                      >ÿÿÿÿÿprivate:
                      >ÿÿÿÿÿÿÿÿÿstringÿscalarÿname
                      >ÿÿÿÿÿÿÿÿÿvoidÿnew()
                      >ÿÿÿÿÿpublic:
                      >ÿÿÿÿÿÿÿÿÿfinalÿvoidÿset()
                      >ÿÿÿÿÿÿÿÿÿfinalÿstringÿscalarÿget()
                      >ÿ}

                      :ÿvoidÿfunctionÿProbe::new()ÿnameÿ=ÿ"Default"

                      :ÿvoidÿfunctionÿProbe::set(stringÿscalarÿname)ÿthis.nameÿ=ÿname

                      :ÿstringÿscalarÿProbe::get()ÿreturn(name)

                      :ÿ
                      :ÿclassÿStorage{
                      >ÿÿÿÿÿprivate:
                      >ÿÿÿÿÿÿÿÿÿstaticÿpointer(classÿProbeÿscalar)ÿscalarÿprobe_ptr
                      >ÿÿÿÿÿpublic:
                      >ÿÿÿÿÿÿÿÿÿfinalÿvoidÿstore()
                      >ÿÿÿÿÿÿÿÿÿfinalÿclassÿProbeÿscalarÿretrieve()
                      >ÿ}

                      :ÿvoidÿfunctionÿStorage::store(classÿProbeÿscalarÿprobe)ÿprobe_ptrÿ=ÿ&probe

                      :ÿclassÿProbeÿscalarÿfunctionÿStorage::retrieve()ÿreturn(*probe_ptr)

                      :ÿ
                      :ÿclassÿTestÿ{
                      >ÿÿÿÿÿprivate:
                      >ÿÿÿÿÿÿÿÿÿfinalÿvoidÿelsewhere()
                      >ÿÿÿÿÿpublic:
                      >ÿÿÿÿÿÿÿÿÿfinalÿvoidÿsameScope()
                      >ÿÿÿÿÿÿÿÿÿfinalÿvoidÿdifferentScope()
                      >ÿ}

                      :ÿvoidÿfunctionÿTest::elsewhere()ÿ{
                      >ÿÿÿÿÿclassÿProbeÿscalarÿoriginalÿ//ÿ<-ÿsameÿnameÿforÿobject:ÿ"original"
                      >ÿÿÿÿÿoriginal.set("Outÿofÿscope")ÿ//ÿ<-ÿbutÿyouÿwon'tÿseeÿthisÿmessage
                      >ÿ}

                      :ÿvoidÿfunctionÿTest::sameScope()ÿ{
                      >ÿ
                      >ÿÿÿÿÿclassÿProbeÿscalarÿoriginal
                      >ÿÿÿÿÿoriginal.set("NotÿDefault")
                      >ÿ
                      >ÿÿÿÿÿclassÿStorageÿscalarÿs
                      >ÿÿÿÿÿs.store(original)
                      >ÿ
                      >ÿÿÿÿÿprintf("Verifyingÿoriginal:ÿ\nÿ%-10s\n\n",ÿs.retrieve().get())
                      >ÿ
                      >ÿÿÿÿÿ//ÿOverwritingÿbyÿusingÿsameÿnameÿwithinÿsameÿscope;ÿyou'llÿseeÿ"Default"
                      >ÿÿÿÿÿoriginalÿ=ÿProbe()
                      >ÿ
                      >ÿÿÿÿÿprintf("Sameÿscope->overwritten:\nÿ%-10s\n\n",ÿs.retrieve().get())
                      >ÿ}

                      :ÿvoidÿfunctionÿTest::differentScope()ÿ{
                      >ÿ
                      >ÿÿÿÿÿclassÿProbeÿscalarÿoriginal
                      >ÿÿÿÿÿoriginal.set("NotÿDefault")
                      >ÿ
                      >ÿÿÿÿÿclassÿStorageÿscalarÿs
                      >ÿÿÿÿÿs.store(original)
                      >ÿ
                      >ÿÿÿÿÿ//ÿHere,ÿsameÿname,ÿbutÿdifferentÿscope;ÿtheÿmessageÿstaysÿ"NotÿDefault"
                      >ÿÿÿÿÿelsewhere()
                      >ÿ
                      >ÿÿÿÿÿprintf("Differentÿscope->notÿoverwritten:\nÿ%-10s\n\n",ÿs.retrieve().get()
                      >ÿ)
                      >ÿ}

                      :ÿ
                      :ÿtestÿ=ÿTest()

                      :ÿtest.sameScope()
                      Verifyingÿoriginal:ÿ
                      ÿNotÿDefault

                      Sameÿscope->overwritten:
                      ÿDefaultÿÿÿ


                      :ÿ
                      :ÿtest.differentScope()
                      Differentÿscope->notÿoverwritten:
                      ÿNotÿDefault


                      :ÿ
                      :ÿnew_storageÿ=ÿStorage()

                      :ÿprintf("%-10s\n\n",ÿnew_storage.retrieve().get())ÿ//ÿ<-ÿstaticÿmemberÿfunction
                      NotÿDefault


                      :ÿ
                      :ÿend
                      --------------------------------------------------------------------------------

                      .ÿ
                      .ÿsetÿlinesizeÿ`line_size'

                      .ÿ
                      .ÿexit

                      endÿofÿdo-file


                      .


                      I've attached the do-file, itself, if you want to explore things on your own, for example, the fact that the pointer is still holding an object I think prevents the first instance from being destroyed despite the function's going out of scope that created it. You'd probably want to put a destructor in the class if you really wanted to get rid of it (or, as mentioned above, forcibly clearing Mata's memory).
                      Attached Files

                      Comment

                      Working...
                      X