Extending a class without using the keyword "extends"

Post your working scripts, libraries and tools for AHK v1.1 and older
Helgef
Posts: 4709
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: 1444
Joined: 13 Aug 2016, 21:04
Contact:

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: 4709
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: 1444
Joined: 13 Aug 2016, 21:04
Contact:

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.
iseahound
Posts: 1444
Joined: 13 Aug 2016, 21:04
Contact:

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

02 Aug 2019, 21:13

Updated for v2 compatibility.

Code: Select all

q := new uber.c
text := q.test1
_text := (A_AhkVersion < 2) ? text : "text"
MsgBox %_text%
q.test2()
q.test3()

; At each base "level" the name of the __class and the number of objects are displayed.
; The ground level should be an empty class with 2 objects.   [_extends, test1]
; The -1 (base) level should be the class "c" with 5 objects. [__Class, __extends, __Init, extends, outer]
; The -2 level should be the class "b" with 6 objects.        [__Class, __extends, __Init, extends, outer, test2]
; The -3 level should be the class "a" with 4 objects.        [__Class, __delete, __New, test3]
; The -4 level is invalid.
; Why does the ground level have an empty class? Because class names are like types - they live in obj.base.
; When a class is instansiated all the old functions go into its base object and its properties (_extends, test1) become the top level.
try level0 := "Ground Level: " ObjRawGet(q, "__class")                 "`nNumber of Objects: " q.Count()
try level1 := "Level -1: " ObjRawGet(q.base, "__class")                "`nNumber of Objects: " q.base.Count()
try level2 := "Level -2: " ObjRawGet(q.base.base, "__class")           "`nNumber of Objects: " q.base.base.Count()
try level3 := "Level -3: " ObjRawGet(q.base.base.base, "__class")      "`nNumber of Objects: " q.base.base.base.Count()
try level4 := "Level -4: " ObjRawGet(q.base.base.base.base, "__class") "`nNumber of Objects: " q.base.base.base.base.Count()

; v2 compatible code.
_level0 := (A_AhkVersion < 2) ? level0 : "level0"
_level1 := (A_AhkVersion < 2) ? level1 : "level1"
_level2 := (A_AhkVersion < 2) ? level2 : "level2"
_level3 := (A_AhkVersion < 2) ? level3 : "level3"
_level4 := (A_AhkVersion < 2) ? level4 : "level4"

; v2 compatible code.
try MsgBox %_level0%
try MsgBox %_level1%
try MsgBox %_level2%
try MsgBox %_level3%
try MsgBox %_level4%

; Necessary or the object will delete itself immediately.
; When the script exits the __delete method will be called.
Sleep 2500

class uber {

   class a {
      __New() {
         text := A_ThisFunc
         _text := (A_AhkVersion < 2) ? text : "text"
         MsgBox %_text%
      }

      __delete() {
         text := A_ThisFunc
         _text := (A_AhkVersion < 2) ? text : "text"
         MsgBox %_text%
      }

      test3() {
         text := "This is a method of class a."
         _text := (A_AhkVersion < 2) ? text : "text"
         MsgBox %_text%
      }
   }

   class b {
   static extends := "a"

      _extends := this.__extends()
      __extends(subbundle := "") {
         object := this.outer[this.extends]
         bundle := ((object.haskey("__extends")) ? object.__extends(true) : object)
         (subbundle) ? (this.base := bundle) : (this.base.base := bundle)
         return (subbundle) ? this : ""
      }

      outer[p:=""] {
         get {
            static period := ".", _period := (A_AhkVersion < 2) ? period : "period"
            if ((__outer := RegExReplace(this.__class, "^(.*)\..*$", "$1")) != this.__class)
               Loop Parse, __outer, %_period%
                  outer := (A_Index=1) ? %A_LoopField% : outer[A_LoopField]
            return IsObject(outer) ? ((p) ? outer[p] : outer) : ((p) ? %p% : "")
         }
      }

      test2() {
         text := "This is a method of class b."
         _text := (A_AhkVersion < 2) ? text : "text"
         MsgBox %_text%
      }
   }

   class c {
   static extends := "b"

      _extends := this.__extends()
      __extends(subbundle := "") {
         object := this.outer[this.extends]
         bundle := ((object.haskey("__extends")) ? object.__extends(true) : object)
         (subbundle) ? (this.base := bundle) : (this.base.base := bundle)
         return (subbundle) ? this : ""
      }

      outer[p:=""] {
         get {
            static period := ".", _period := (A_AhkVersion < 2) ? period : "period"
            if ((__outer := RegExReplace(this.__class, "^(.*)\..*$", "$1")) != this.__class)
               Loop Parse, __outer, %_period%
                  outer := (A_Index=1) ? %A_LoopField% : outer[A_LoopField]
            return IsObject(outer) ? ((p) ? outer[p] : outer) : ((p) ? %p% : "")
         }
      }

      test1 := "This is a property of class c."
   }
}
Hopefully my idea of what a bundle is correct. Math is not easy.

Return to “Scripts and Functions (v1)”

Who is online

Users browsing this forum: No registered users and 152 guests