Extending a class without using the keyword "extends"

Post your working scripts, libraries and tools
Helgef
Posts: 3303
Joined: 17 Jul 2016, 01:02
Contact:

Re: Extending a class without using the keyword "extends"

31 Aug 2018, 01:56

Code: Select all

; returns this if called via another _init() function. 
return this
There is only one point of return, so this always happens, and creates circular references. You never call __init, so the derived object doesn't get properly initialised. It doesn't matter in your particular example since the bases you extend doesn't define any instance variables (other than _init).

Maybe, in v1, base.__init() should be added to __init unconditionally, currently it is only added if you actually use the extends keyword. The drawback would be an extra call when deriving objects from classes which doesn't dynamically changes their base. For v2, I think much more elaborate changes would be preferable, we will see what happens.

I guess I'm a nerd, I like your code :ugeek:

Cheers.
iseahound
Posts: 355
Joined: 13 Aug 2016, 21:04
GitHub: iseahound

Re: Extending a class without using the keyword "extends"

31 Aug 2018, 02:34

Thanks a lot, Helgef.

I'm not sure if I understand the point about "circular references" but I can assure you that this code takes advantage of the fact that __Init() the meta function is not called. So if I were to make it properly initialize instance variables, it'd break! Anyhow, instance variables aren't a big deal, especially since static variables will work.

Oh, and also I keep the "outer" property seperate since I've found it to be useful, in general. I appreciate your suggestions, criticisms, and remarks greatly.
Helgef
Posts: 3303
Joined: 17 Jul 2016, 01:02
Contact:

Re: Extending a class without using the keyword "extends"

31 Aug 2018, 02:55

I'm not sure if I understand the point about "circular references"
Each instance has a reference to itself, via the key init. You might notice that __delete is not called. Try msgbox % q == q.init
Anyhow, instance variables aren't a big deal, especially since static variables will work.
No issue then :thumbup:
Oh, and also I keep the "outer" property seperate since I've found it to be useful, in general.
:thumbup:

Cheers.
iseahound
Posts: 355
Joined: 13 Aug 2016, 21:04
GitHub: iseahound

Re: Extending a class without using the keyword "extends"

20 Nov 2018, 22:34

Final Version v2:

Code: Select all

q := new uber.c
MsgBox % q.test1
q.test2()
q.test3()

try MsgBox % "Ground Level: " ObjRawGet(q, "__class")                 "`nNumber of Objects: " q.Count()
try MsgBox % "Level -1: " ObjRawGet(q.base, "__class")                "`nNumber of Objects: " q.base.Count()
try MsgBox % "Level -2: " ObjRawGet(q.base.base, "__class")           "`nNumber of Objects: " q.base.base.Count()
try MsgBox % "Level -3: " ObjRawGet(q.base.base.base, "__class")      "`nNumber of Objects: " q.base.base.base.Count()
try MsgBox % "Level -4: " ObjRawGet(q.base.base.base.base, "__class") "`nNumber of Objects: " q.base.base.base.base.Count()

Sleep 2500

class uber {

   class a {
      __New() {
         MsgBox % A_ThisFunc
      }

      __delete() {
         MsgBox % A_ThisFunc
      }

      test3() {
         MsgBox % "This is a method of class a."
      }
   }

   class b {
   static extends := "a"
      
      _extends := this.__extends()
      __extends(endofunctor := "") {
         under := ((___ := this.outer[this.extends].__extends(true)) ? ___ : this.outer[this.extends])
         (endofunctor) ? (this.base := under) : (this.base.base := under)
         return (endofunctor) ? this : ""
      }

      outer[p:=""] {
         get {
            if ((_class := RegExReplace(this.__class, "^(.*)\..*$", "$1")) != this.__class)
               Loop, Parse, _class, .
                  outer := (A_Index=1) ? %A_LoopField% : outer[A_LoopField]
            if IsObject(outer)
               return (p) ? outer[p] : outer
            else
               return (p) ? %p% : ""
         }
      }

      test2() {
         MsgBox % "This is a method of class b."
      }
   }

   class c {
   static extends := "b"

      _extends := this.__extends()
      __extends(endofunctor := "") {
         under := ((___ := this.outer[this.extends].__extends(true)) ? ___ : this.outer[this.extends])
         (endofunctor) ? (this.base := under) : (this.base.base := under)
         return (endofunctor) ? this : ""
      }

      outer[p:=""] {
         get {
            if ((_class := RegExReplace(this.__class, "^(.*)\..*$", "$1")) != this.__class)
               Loop, Parse, _class, .
                  outer := (A_Index=1) ? %A_LoopField% : outer[A_LoopField]
            if IsObject(outer)
               return (p) ? outer[p] : outer
            else
               return (p) ? %p% : ""
         }
      }

      test1 := "This is a property of class c."
   }
}
Code has been updated and cleaned! New Breakdown:

Code: Select all


; Assume that class "big" is on the same level as "small"
class big {
; static variable to be declared once.
static extends := "small"

   ; Instance variable are only evaluated when using the "new" operator.
   ; (Note that the "new" operator calls the __Init() & __New() meta functions.)
   _extends := this.__extends()

   ; parameter = true means that this class has been instantiated.
   __extends(endofunctor := "") {

      ; If the class to be extended also has an __extends() function, call that.
      ; Note that IsFunc() cannot be used, so a work around is to run the function directly, 
      ; save it to a temporary variable "___" and then check if ___ holds a value. 
      ; We can rewrite the following line as:
      ; ___ := this.outer[this.extends].__extends(true)
      ; if (___)
      ;     under := ___
      ; else
      ;     under := this.outer[this.extends]
      ; if we remove the temporary variable ___ we would have to call __extends() twice which causes issues.
      under := ((___ := this.outer[this.extends].__extends(true)) ? ___ : this.outer[this.extends])

      ; If the class was instantiated with "new", set the base of the base. 
      ; else if the class was not instantiated, and base is empty and can be set.
      (endofunctor) ? (this.base := under) : (this.base.base := under)

      ; returns this if called via another __extends() function. 
      return (endofunctor) ? this : ""
   }
   
   ; see: https://autohotkey.com/boards/viewtopic.php?t=46828
   outer[p:=""] {
      get {
         if ((_class := RegExReplace(this.__class, "^(.*)\..*$", "$1")) != this.__class)
            Loop, Parse, _class, .
               outer := (A_Index=1) ? %A_LoopField% : outer[A_LoopField]
         if IsObject(outer)
            return (p) ? outer[p] : outer
         else
            return (p) ? %p% : ""
      }
   }
}
EDIT: removed parenthesis (this.outer)[this.extends] to this.outer[this.extends] to properly send the argument to the property. Fixes the un-nested case.
EDIT 2: removed circular references, allowing __Delete to work normally.

Return to “Scripts and Functions”

Who is online

Users browsing this forum: colt and 26 guests