Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

[AHK_L] Customizing Object() and Array()


  • Please log in to reply
5 replies to this topic
Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006

Pre-requisites: AutoHotkey_L and some experience with objects.

A user recently asked for a way to add methods to all objects that are created with [], {}, Object() or Array(). This currently isn't fully supported, but is possible with the following caveats:

  • It may stop working in a future version (but not until a better alternative is provided).
  • It doesn't apply to "parameter arrays" created by calling a variadic function.
  • Additional lines will show in ListLines each time you create an object if ListLines is enabled.
  • User-defined classes will not automatically inherit behaviour defined by Object(). However, they can explicitly inherit behaviour via the extends keyword (see example below).

To achieve it, we take advantage of:

  • the ability to override any built-in function with a user-defined one.
  • the undocumented fact that defining a function named Array overrides [] and Object overrides {}.

However, once a built-in function is overridden, there's no way to call the original function. This is where two of the caveats mentioned above become useful. There are at least two methods to create an object without invoking Object() or Array():

  • Define and call a variadic function. The final parameter receives an object (an array of parameters).
  • Define a class and use the new keyword to create an object derived from it.

To demonstrate, I will utilize both methods to implement commonly desired features:

  • Add Array.length(), identical to MaxIndex except that it returns 0 instead of an empty string if there are no items.
  • Prevent Object.Remove() from decrementing keys when an integer key is removed.
; Define the base class.
class _Array {
    length() {
        return Round(this.MaxIndex()) ; Round() turns "" into 0.
    }
}
; Redefine Array().
Array(prm*) {
    ; Since prm is already an array of the parameters, just give it a
    ; new base object and return it. Using this method, _Array.__New()
    ; is not called and any instance variables are not initialized.
    prm.base := _Array
    return prm
}

; Demonstrate.
arr := ["a", "b"]
MsgBox % arr.length()
; Define the base class.
class _Object {
    Remove(prm*) {
        if prm.MaxIndex() = 1 {
            k := prm[1]
            if k is integer
                return ObjRemove(k, "")
        }
        return ObjRemove(prm*)
    }
}
; Redefine Object().
Object(prm*) {
    ; Create a new object derived from _Object.
    obj := new _Object
    ; For each pair of parameters, store a key-value pair.
    Loop % prm.MaxIndex()//2
        obj[prm[A_Index*2-1]] := prm[A_Index*2]
    ; Return the new object.
    return obj
}

; Demonstrate.
x := {a: "b", 1: "one", 2: "two"}
MsgBox % x.a
MsgBox % x[2]
x.Remove(1)
; Without the override, x[1] is "two" and x[2] doesn't exist.
; With the override, x[1] is gone and x[2] is still "two".
MsgBox % x[2]
; Define a custom class inheriting standard properties from _Object.
class MyClass extends _Object {
    ;...
}

Note that _Object.Remove() will still affect integer keys if a range is removed. For example, obj.Remove(1, 10) will remove items 1 to 10 and cause obj[11] to become obj[1]. Fixing this is left as an exercise for the reader.



Bentschi
  • Moderators
  • 120 posts
  • Last active: Sep 05 2014 02:12 AM
  • Joined: 26 Nov 2008
I was using the Object-Function like this, but if i want to get the Pointer of the Object like...
obj := {a:1, b:2, c:3}
ptr := Object(obj)
obj := Object(ptr)
... it want work!
Here is my workaround for this...
Warning: The code posted here does not work correctly. See next post.
Object(p*)
{
  o := new MyObject()
  if (p.maxIndex()=1)
  {
    r := (IsObject(p[1])) ? &(p[1]) : new (p[1])
    rc := (IsObject(p[1])) ? ObjAddRef(&(p[1])) : ObjRelease(p[1])
    return r
  }
  Loop % p.maxIndex()//2
    o[p[A_Index*2-1]] := p[A_Index*2]
  return o
}

Edit: I have modified the code, because the ObjRelease(p[1]) should not be called before new (p[1]), if i am right.
Edit2: The code posted here does not work correctly. See next post.

Bentschi
  • Moderators
  • 120 posts
  • Last active: Sep 05 2014 02:12 AM
  • Joined: 26 Nov 2008
Sorry, i thought my code posted above will work, because...
MsgBox, % obj "`n" Object(Object(Object(obj)))
... was both the same, but if i use...
copy := Object(Object(obj))
I cant access anything of copy.

Therefore the new (address) might not work correctly.
Is there any other way?

Bentschi
  • Moderators
  • 120 posts
  • Last active: Sep 05 2014 02:12 AM
  • Joined: 26 Nov 2008
Now i found a way...
Object(p*)
{
  [color=#FF0000]static ref := [], rc := [][/color]
 [color=#FF0000] if (p.maxIndex()=1)
  {
    if (IsObject(p[1]))
    {
      ref[r := &(p[1])] := p[1]
      rc[r] := (rc[r]) ? rc[r]+1 : 1
    }
    else
    {
      r := ref[p[1]]
      rc[p[1]] := (rc[p[1]]) ? rc[p[1]]-1 : 0
      if (!rc[p[1]])
        ref.delete(p[1])
    }
    return r
  }[/color]
  o := new MyObject()
  Loop % p.maxIndex()//2
    o[p[A_Index*2-1]] := p[A_Index*2]
  return o
}
If the Object never gets lost, it must not get recreated :D

R3gX
  • Members
  • 307 posts
  • Last active: Dec 29 2013 04:50 PM
  • Joined: 28 Feb 2011

Hi everyone!

 

As I'm learning advanced techniques for objects, here is my solution for the "object exercise" from Lexikos

; Define the base class.
class _Object
{
    Remove(prm*)
    {
        If (prm.MaxIndex() = 1)
        {
            k := prm[1]
            If k is integer
                Return, ObjRemove(this, k, "")
            Else
                Return, ObjRemove(this, k)
        }
        Else If (prm.MaxIndex()>1)
        {
            If (prm[1]=prm[1]*1 && prm[2]=prm[2]*1) ; numeric parameters
            {
                min := Round(prm[1]) , max := Round(prm[2])
                Range := []
                Loop, % (max-min)+1
                    Range.Insert((min-1)+A_Index)
                For k,v in Range
                     ObjRemove(this, v, "")
            }
            Else
                Return, ObjRemove(this, prm*)
        }
        Else
            ObjRemove(this)
    }
}

; Redefine Object().
Object(prm*)
{
    ; Create a new object derived from _Object.
    obj := new _Object
    ; For each pair of parameters, store a key-value pair.
    Loop % prm.MaxIndex()//2
        obj[prm[A_Index*2-1]] := prm[A_Index*2]
    ; Return the new object.
    return obj
}

x := {a: "A", 1: "one", b:"B", 2: "two", c:"C"
    , 3: "three", d: "D", 4: "four", e: "E", 5: "five"}


; Remove keys from 1 to 3
x.Remove(1, 3)
MsgBox, % Ini(x, "x")

; Do nothing due to different types of keys
x.Remove(4, "c")
MsgBox, % Ini(x, "x")

; Remove keys from "a" to "c"
x.Remove("a", "c")
MsgBox, % Ini(x, "x")

; Remove the key 4
x.Remove(4)
MsgBox, % Ini(x, "x")

; Remove the key "d"
x.Remove("d")
MsgBox, % Ini(x, "x")

; Remove the last key (5)
x.Remove()
MsgBox, % Ini(x, "x")


ExitApp
!&::ExitApp


Ini(obj, ObjName)
{
    If !IsObject(obj)
        Return
    If !StrLen(ObjName)
        ObjName := "Object"
    full_obj := "[" ObjName "]"
    For k,v in obj
        If !IsObject(v)
            full_obj .= "`n" k " = " v
    Return, full_obj
}

Edited by R3gX, 09 March 2013 - 12:22 PM.

signature.png
Previously known as TomXIII
AutoHotkey version : 1.1.10


AutomateEverything
  • Members
  • 60 posts
  • Last active: Aug 25 2014 06:02 AM
  • Joined: 03 Dec 2012

Maybe a stupid question, but why don't name the classes Object and Array (without underscore)?