(with Adam Bell)
Introduction
A constructor is a man with a hammer who builds things but it can also be a script object in a handler; a rarely used AppleScript “trick” but extremely useful when needed.
Basically, constructors are handlers containing embedded script objects which act as templates for later use in the body of a script. For example, the constructor (the template) could be a super-hero, and we would use it to make a Superman, a Batman and a Spiderman by changing its properties. Typically, constructors will return an “object” (a script object), which is the thing you just constructed with it, but the key to this is that the script object is reinitialized every time the handler it is in is called.
In Matt Neuburg’s “AppleScript: The definitive Guide, Second Edition” on page 155, he discusses Constructors, and he includes an example which is elaborated slightly here that illustrates the power of the idea in a simple way. As he says in his text “This approach is using a sledgehammer to kill a fly, but it’s a great example”
Matt Neuburg’s Example
-- Set up a handler containing a script containing a counter
-- Every time newCounter is called, the aCounter script is
-- reinitialized as a new instance, i.e., the new counter
-- that results starts off at 0.
-- First, the handler with the template:
on newCounter()
script aCounter
property c : 0
on increment()
set c to c + 1
end increment
end script
return aCounter
end newCounter
-- Now set up a counter using newCounter()
set counter_1 to newCounter() --> an instance of aCounter
-- count stuff using Counter_1
repeat 2 times
counter_1's increment() -- advances the count
end repeat -- count_1 is now 2
-- set up a second counter, again using newCounter()
set counter_2 to newCounter() --> a new, separate instance
-- count stuff using Counter_2
repeat 6 times
counter_2's increment()
end repeat -- counter_2 is now 6
-- But counter_1 still exists and we can use it again:
repeat 2 times
counter_1's increment()
end repeat -- counter_1 is now 4 - it has retained the 2 from before
-- Check the counts:
display dialog "Count_1 is now " & counter_1's c & return & return & "Count_2 is now " & counter_2's c buttons {"Neat"} default button 1 with icon 1
Let’s go to another example (by JJ Sancho):
to buildProtype(theName, theAge, eyeColor)
script prototype
property n : theName
property x : theAge
property y : eyeColor
to Introduce()
display dialog "My name is " & n & ", I'm " & x & " years old and I have " & y & " eyes." with icon 1
end Introduce
end script
end buildProtype
set Lisa to buildProtype("Elizabeth", 17, "brown")
set Rob to buildProtype("Robert", 42, "green")
tell Lisa to Introduce()
tell Rob to Introduce()
-- and we can change Lisa or Rob like this
set Rob's n to "Bob (not Robert)"
set Lisa's x to "going on 18"
tell Rob to Introduce()
tell Lisa to Introduce()
Thanks to our constructor, we built two persons called “Lisa” and “Rob”, each with their own characteristics. Now we have two independent objects which have inherited three different properties (the parameters we passed to the constructor about their name, age and eye color), and a handler (introduce) we can use at any time, and as illustrated in the script, we can change those properties independently.
As you see, we’ve mentioned the keyword “prototype”, used in various languages with a standard meaning. in AppleScript, there are no real documents about prototypes, but we can build a kind of pseudo-prototype (like the example above). We could call the script object containing the “constructor” our prototype.
In most AppleScripting, constructors (or “prototypes”) are not a used very often, but we will try to show here a semi-real situation where we might use them:
JJ was waiting for a male child to be born. His family needed a name for him and had a long list of boy’s names. His family are Barcelona Football Club fans, and Ronaldo migrated to the Real Madrid, so we want every name starting with “Ronald” to be automatically deleted from our name list. Let’s build a quick how-to:
set maleNames to {"Romario", "Bebeto", "Kareka", "Ronaldo", "Reiziger", "Rivaldo", "Ronaldinho", "Juninho", "Suker", "Xavi"}
cleanTraitors(maleNames)
--> {"Romario", "Bebeto", "Kareka", "Reiziger", "Rivaldo", "Juninho", "Xavi"}
to cleanTraitors(namesList)
script folks
property suspect : namesList
property guiltless : {}
end script
repeat with i in folks's suspect
if i does not start with "Ronald" then set end of folks's guiltless to i's contents
end repeat
return folks's guiltless
end cleanTraitors
Voil’a! We get back a list of guiltless names
And you’ll ask yourself, oh man: why go to such complication? It was good enough a simple “repeat” loop… Yes. But the repeat-loop iteration will be much faster if we enclose the guiltless list in a script object’s property. And using a constructor, we will make the list a property automatically. When we pass the parameter to the handler, this parameter is being copied into the nested script object called “folks”.
If you try this idea with a 7000 item-list, you will see a huge difference in the time it takes to complete because of the way AppleScript handles script objects, our constructors.
Thanks to this constructor, we’ve designed a very fast and efficient handler. This is not the only use for constructors, but it’s a good example. When you have a huge list to build or alter, use a script object.
A final example will drive home the point. In the "AppleScript Guidebook there is an ASCII sort handler called ASCII_Sort(aList). It’s very straight-forward, but slows down substantially as the list being sorted gets longer. This modification will speed it up substantially for large lists. The idea is that we’ve embedded the lists that will be frequently visited in the handler with script properties that can be accessed more quickly.
set composer_list to {"Ellington", "Sibelius", "Copland", "Bach", "Mozart"}
ASCII_Sort(composer_list)
on ASCII_Sort(my_list)
script S
property IL : {} -- index list
property SL : {} -- sorted list
property ML : my_list
end script
repeat (the number of items in S's ML) times
set the low_item to ""
repeat with i from 1 to (number of items in S's ML)
if i is not in the S's IL then
set this_item to item i of S's ML as text
if the low_item is "" then
set the low_item to this_item
set the low_item_index to i
else if this_item comes before the low_item then
set the low_item to this_item
set the low_item_index to i
end if
end if
end repeat
set the end of S's SL to the low_item
set the end of the S's IL to the low_item_index
end repeat
return S's SL
end ASCII_Sort
Obviously, for the example given, the improvement is miniscule, but for a very large list it is substantial. If sorting a very large list is important, however, I recommend this QuickSort algorithm (item #2 in the thread) by Nigel Garvey and Arthur Knapp in the Code Exchange section of bbs.applescript.net. It is blindingly fast and it uses the technique discussed here: a script in a handler.