Announcement

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

  • Method/Function Chaining

    Working on a project where I want to keep the Mata library as consistent with the original library it is wrapping as consistently as possible and was wondering how to access classes that are initialized by methods within a class. For example:

    Code:
    mata:
    
    mata clear
    
    class B {
        private string scalar mydatum
        public  string scalar get()
        void                   init(), modifier()
    }
    
    void B::init(string scalar vnm, | string scalar arguments) {
        if (arguments != "") {
            this.mydatum = "var " + vnm + " = " + arguments
        }
        else {
            this.mydatum = vnm 
        }
    }
    
    void B::modifier(string scalar content) {
        this.mydatum = this.get() + ".someContentMethod(" + content + ")"
    }
    
    string scalar B::get() {
        return(this.mydatum)
    }
    
    class A {
        private string scalar originalDatum
        public  string scalar get()
        void                  init(), modifier()
        class     B       scalar newb()
    }
    
    void A::init(string scalar vnm, string scalar arguments) {
        this.originalDatum = "var " + vnm + " = " + arguments
    }
    
    void A::modifier(string scalar contents) {
        this.originalDatum = this.get() + ".originalMethod(" + contents + ")"
    }
    
    string scalar A::get() {
        return(this.originalDatum)
    }
    
    class B scalar A::newb(| string scalar v, string scalar a) {
        class B scalar newClassB
        newClassB = B()
        if (v != "" & a != "") {
            newClassB.init(v, a)
        }
        else if (v != "" & a == "") {
            newClassB.init(v)
        }
        else {
            newClassB.init(this.get())
        }
        return(newClassB)
    }
    
    end
    Creates two classes with methods that are similar to what I am working with. When I try testing some stuff interactively I end up getting an error that I'm having difficulty understanding:

    Code:
    . mata
    ------------------------------------------------- mata (type end to exit) -----------------------------
    : x = A()
    
    : x.init("thisvar", "theseContents")
    
    : x.get()
      var thisvar = theseContents
    
    : x.newb("newObject", "parameterArgument").modifier("anotherMethod").get()
    type mismatch:  exp.exp:  transmorphic found where struct expected
    r(3000);
    
    : x.newb("newObject", "parameterArgument").get()
      var newObject = parameterArgument
    
    : y = x.newb("newObject", "parameterArgument")
    type mismatch:  transmorphic = class not allowed
    r(3000);
    I was wondering if this is a place where I need to be using pointers or if there is some other technique that people tend to use more frequently to accomplish similar goals? Ideally, one would be able to do something like: (pseudo code loosely based on the examples above)

    Code:
    mata:
    
    x = A()
    
    x.init(`""myvar""', `""args""')
    
    x.newb(`""newVar""', `""moreArgs""').modifier("changeSomething").otherModifier("toSomethingElse").get()

  • #2
    Maybe someone from StataCorp could add something about how the syntax is processed internally to the Mata documentation? It isn't clear why the approach above fails and similar syntax is legal in many other languages (e.g., Java, Python, JavaScript, etc...). Although the question does not seem to have come up previously, it would be nice to have some understanding of why this fails and/or what existing functionality would provide the desired effect.

    Comment


    • #3
      This is caused by a language limitation in Mata's interactive mode. I will look into getting this added but it won't be an easy fix, i.e., I can't say when it will be added.

      Comment


      • #4
        Hua Peng (StataCorp),

        I just tested something similar from a do file and got the same error. Does this mean it would only be possible to chain the methods together if they are compiled? Are there any ways to work around this that you're aware of that I could try? Without chaining the wrapper I'm working on gets fairly verbose very quickly and it stops resembling the API for the original library.

        Thanks again,
        Billy

        Comment


        • #5
          Currently neither interactive mode nor compile mode will allow this to work. I can not think of any alternative method beside explicitly declaring and assigning variables for each intermediate stage either.

          Hua

          Comment


          • #6
            Hua Peng (StataCorp) is it possible to define a function/method that wouldn't require parentheses to simulate a pipe operator?

            Comment


            • #7
              I do not think it can be done. Maybe there exists some clever trick of using Macro expansion but I do not see how.

              Comment


              • #8
                Hua Peng (StataCorp) thanks for the feedback. I guess things will stay a bit on the verbose side but hopefully will have something interesting soon.

                Comment


                • #9
                  William, have you made any additional progress on this issue? I'm working on another project that will require a lot of method chaining.

                  I think you may be encountering two different issues.

                  Code:
                  x.newb("newObject", "parameterArgument").modifier("anotherMethod").get()
                  modifier() returns void, which is to say it returns J(0, 0, .) (help m2 syntax). J(0, 0, .) doesn't have a get() method, so I don't think this would work. Maybe modifier() should return this?

                  Code:
                  y = x.newb("newObject", "parameterArgument")
                  I think this is a declaration issue. See http://www.statalist.org/forums/foru...anding-structs for a related discussion. Basically, you can't declare variables in interactive mode, but without that, various Mata errors can arise when working with structs and classes.

                  Comment


                  • #10
                    Matthew White the classes/methods in question are used to modify a private member in a way that is analogous to the StringBuilder class in Java (e.g., you can continue adding elements to the object). So in this case, the methods all return void because they are modifying internal members of the class. Given Hua Peng (StataCorp)'s response, I just assumed that method/function chaining was far enough removed from the original language design that trying to implement it would require a non-trivial investment to add a feature that not everyone may want/need. So, it will make using the code a bit more verbose, but should still retain - to some extent - the general design of the original API that the classes/methods are wrapping.

                    Comment


                    • #11
                      When you use method chaining, each method needs to return an object. StringBuilder uses a pattern called "method cascading," where each call returns the StringBuilder object. For example, StringBuilder.append() doesn't return void, it returns StringBuilder: it returns itself. You can see this in the StringBuilder source code:

                      http://www.docjar.com/html/api/java/...lder.java.html

                      You can see that many methods return this. I think you could do something similar if you have A.modifier() return class A scalar, i.e., return this. It still won't always work in interactive method, but it should work within functions/methods.

                      Comment


                      • #12
                        Matthew White I'll try looking into that as well to see if it helps. I was hoping that it would be something that could be used in interactive mode, but it would still make things easier/less verbose if it works for compiling functions/new methods. The project is wrapping a JavaScript library and I was trying to get the Mata interface to resemble the JS library as close as possible; this way if user's already had existing code using the library it would be easy for them to slide it into the Stata environment.

                        Comment


                        • #13
                          I think most statements would work in interactive mode. I don't quite understand the constraints of interactive mode: they seem a little inconsistent. For example, say you define A and B so that modifier() returns class scalars:

                          Code:
                          mata:
                          
                          mata clear
                          
                          class B {
                              private string scalar mydatum
                              public  string scalar get()
                              void                   init()
                              class B scalar modifier()
                          }
                          
                          void B::init(string scalar vnm, | string scalar arguments) {
                              if (arguments != "") {
                                  this.mydatum = "var " + vnm + " = " + arguments
                              }
                              else {
                                  this.mydatum = vnm 
                              }
                          }
                          
                          class B scalar B::modifier(string scalar content) {
                              this.mydatum = this.get() + ".someContentMethod(" + content + ")"
                              return(this)
                          }
                          
                          string scalar B::get() {
                              return(this.mydatum)
                          }
                          
                          class A {
                              private string scalar originalDatum
                              public  string scalar get()
                              void                  init()
                              class A scalar modifier()    
                              class     B       scalar newb()
                          }
                          
                          void A::init(string scalar vnm, string scalar arguments) {
                              this.originalDatum = "var " + vnm + " = " + arguments
                          }
                          
                          class A scalar A::modifier(string scalar contents) {
                              this.originalDatum = this.get() + ".originalMethod(" + contents + ")"
                              return(this)
                          }
                          
                          string scalar A::get() {
                              return(this.originalDatum)
                          }
                          
                          class B scalar A::newb(| string scalar v, string scalar a) {
                              class B scalar newClassB
                              newClassB = B()
                              if (v != "" & a != "") {
                                  newClassB.init(v, a)
                              }
                              else if (v != "" & a == "") {
                                  newClassB.init(v)
                              }
                              else {
                                  newClassB.init(this.get())
                              }
                              return(newClassB)
                          }
                          Then all these statements work for me in interactive mode:

                          Code:
                          x = A()
                          x.init("thisvar", "theseContents")
                          x.get()
                          x.newb("newObject", "parameterArgument").modifier("anotherMethod").get()
                          x.newb("newObject", "parameterArgument").get()
                          As you can see, the method chaining seems to work. However, this statement fails, because there's apparently a constraint on class assignment in interactive mode:

                          Code:
                          : y = x.newb("newObject", "parameterArgument")
                          type mismatch:  transmorphic = class not allowed
                          r(3000);

                          Comment


                          • #14
                            Matthew White Thanks for the suggestion. I just tried it and it seems to work reasonably well thus far. The only difference will be how the object gets initialized, but:

                            Code:
                            mata:
                            
                            : x = d3()
                            
                            : x.init("mynewvar")
                            
                            : x.select("body").selectAll("div").enter().data("somedata").complete()
                              var mynewvar = d3.select("body").selectAll("div").enter().data(somedata);
                            Clearly it is just a simple example, but it looks like it may solve things. Still need to tweak a few things, but if you were interested, the repository is in: https://github.com/wbuchanan/d3mata.

                            Comment

                            Working...
                            X