Script Objects

This is one for the AS geniuses in here… Script Objects.

I have read Chris Moore’s unScripted article and Matt Neuberg’s take on them but I am having a very difficult time conceptualizing the implementation of these things. Since I’ve spent a long time building purely procedural Applescript workflows, anything that smacks of object oriented programming is accompanied by the appearance of a throbbing vein in my forehead and a slightly charred smell. :smiley: Despite the appearance of their power they don’t seemed to be widely used (even in here) and I suspect that conceptualizing OOP is much less intuitive that step-by-step instructions.

I really feel that I need to grasp this stuff, not only for AS but to reach into ObjC as well. I think I can make my scripts even more flexible and possibly much faster/smarter.

Any help / advice on this is very welcome. My second born shall bear your name(s).:stuck_out_tongue:

TIA,
Jim Neumann
BLUEFROG

Script objects are probably most often used in scripts to speed access to large lists. Because the script is held in memory, access to the list is faster than access to a normal script list (esoteric details beyond me to recall). Here’s an example with an embedded script


to sort2Lists(sortList, SecondList) -- sorts on the first, keeps the second in sync.
	-- This version is much faster than the other if the lists are large.
	script O
		property L1 : missing value
		property L2 : missing value
	end script
	set O's L1 to sortList
	set O's L2 to SecondList
	tell (count O's L1) to repeat with i from (it - 1) to 1 by -1
		set s to (O's L1)'s item i
		set r to (O's L2)'s item i
		repeat with i from (i + 1) to it
			tell (O's L1)'s item i to if s > it then
				set (O's L1)'s item (i - 1) to it
				set (O's L2)'s item (i - 1) to (O's L2)'s item i
			else
				set (O's L1)'s item (i - 1) to s
				set (O's L2)'s item (i - 1) to r
				exit repeat
			end if
		end repeat
		if it is i and s > (O's L1)'s end then
			set (O's L1)'s item it to s
			set (O's L2)'s item it to r
		end if
	end repeat
	return {O's L1, O's L2}
end sort2Lists

Here’s the same sort two lists routine without the internal script:


to sort2Lists(sortList, SecondList) -- sorts on the first, keeps the second in sync.
	tell (count sortList) to repeat with i from (it - 1) to 1 by -1
		set s to sortList's item i
		set r to SecondList's item i
		repeat with i from (i + 1) to it
			tell sortList's item i to if s > it then
				set sortList's item (i - 1) to it
				set SecondList's item (i - 1) to SecondList's item i
			else
				set sortList's item (i - 1) to s
				set SecondList's item (i - 1) to r
				exit repeat
			end if
		end repeat
		if it is i and s > sortList's end then
			set sortList's item it to s
			set SecondList's item it to r
		end if
	end repeat
	return {sortList, SecondList}
end sort2Lists

Here’s Nigel Garvey’s Lotsa comparitor (requires GetMilliSec osax)


set Ratios to lotsa(500)

on lotsa(many)
	-- Any other preliminary values here.
	
	-- Dummy loop to absorb a small observed
	-- time handicap in the first repeat.
	repeat many times
	end repeat
	
	-- Test 1.
	set t to GetMilliSec
	repeat many times
		-- First test code or handler call here.
		
	end repeat
	set t1 to ((GetMilliSec) - t) / 1000
	
	-- Test 2.
	set t to GetMilliSec
	repeat many times
		-- Second test code or handler call here.
		
	end repeat
	set t2 to ((GetMilliSec) - t) / 1000
	
	-- Timings.
	return {t1, t2, t1 / t2, t2 / t1}
end lotsa

If you prepare a big list of some sort and compare these two, you’ll see that the script object version is waaay faster. Doesn’t make much difference for short lists, so not worth all the (O’s L1)'s item blah bumpf.

One of the reasons you don’t see script objects used to their full potential here is that these fora usually deal with individual sticking points that people have. Script objects are more often to do with the structure of the script itself, up at the “big picture” end of the scale.

Another reason is that, unless you’re an OOP zealot or are writing a large library of reusable code that can be loaded into scripts while they’re running, you’re unlikely to need them.

As Adam says, they’re often used to speed up access to items in lists. But this isn’t their main brief. It just happens that their use allows allows a list variable to be referenced (‘item i of thisListVariable of thisScriptObject’), which happens to bypass the safety checks that slow down access to items when the list variable’s used directly (‘item i of thisListVariable’).

Another “speeding-up” technique has recently come to light where the use of a script object is more in line with its identity as a mini-script in its own right. Script applets that target other applications usually run slower than compiled scripts doing the same job. This is because inter-application communication is relatively slow on a Mac. However, communication between OSAXen and applications isn’t so bad, so a script applet might have some code targetting another application in a script object, which could be run as a script in its own right by the StandardAdditions command ‘run script’.

-- Suppose this example is part of a script running as an applet.

script o
	tell application "My App" -- Pseudocode.  :)
		Do this
		And this
		And that
		And the other
	end tell
end script

-- This applet passes the script object 'o' to the StandardAdditions command
-- 'run script', which runs it on its behalf, reporting back when it's finished.
-- This is faster than having the applet itself sending the commands to the
-- application and waiting for the individual responses
run script o

I find a script object in a handler useful for keeping things tidy when I want to write a recursive process that requires an initial set up. I make the recursive part a separate handler in a script object within the main handler for the process.

on someProcess(x, y, z)
	script o
		property a : x

		on recursiveBit(y, z)
			-- Blah blah blah.
			
			if (y + 1 < z) then recursiveBit (y+1, z - 1)
		end recursive bit
	end script

	-- Initial set-up.
	if (y > z) then set {x, y} to {y, z}

	-- Go into the recursion.
	o's recursiveBit(x, y)
end someProcess

Script objects can also be used to pass snippets of code to a handler in order to customise part of what it does. This saves having to foresee every possible situation when you’re writing the handler and also saves having to write a lot of time-consuming ‘ifs’. Alternatively, it saves having to write several handlers that are only slightly different from each other.

For instance, there’s a sort handler here that receives a script object containing the code that compares the items being sorted. The items will need to be compared differently according to whether they’re lists or simple objects and depending on the order in which they’re supposed to be sorted. The sort handler simply passes the items to the script object’s comparison routines and gets back a decision about which one item’s the greater or smaller.

Hopefully this’ll give you some idea of what can be done with script objects without going too deeply into the niceties of OOP. You can also read the AppleScript Language Guide’s coverage of them.

I hope your second-born’s a boy. :wink:

Great help, gentlemen.

Heretofore the child shall be called Adam Nigel Neumann. And the crowd rejoiced… On second thought, maybe I ought to tell my wife about the deal I just made. I’m thinking she might not want to have another child just to square my debts…

Henceforth, the smallest finger of my left hand shall be known as Phalange Bell-Garvey. (Like Comet Shoemaker-Levy :D)

Thanks Guys.
-Jim

Learning OOP and learning how to use script objects to write object-oriented code are really two different things. (BTW, no offense to Adam or Nigel, but while the code they show may use script objects, it isn’t OOP.) OOP is a a general design technique for better organising larger programs by grouping complex data structures and the functions that operate directly on them together as self-contained objects. Script objects just happen to be the AppleScript language feature that allows you to do that sort of grouping, and therefore write object oriented code, although they’re also useful for implementing other things as well, such as callbacks, modules, and performance hacks (as Adam and Nigel’s examples show).

The reason you don’t see much OOP being done in AS is that while learning language features is easy, learning good design skills is somewhat harder, and most ASers don’t have the time/inclination/resources/need to learn more advanced design skills. That in turn means there’s not a lot of existing AppleScript-specific literature, example code and common knowledge on the subject, which makes it very hard to learn OOP unless you’re willing to look outside AppleScript for information, examples and advice. It’s a bit of a Catch-22.

In my case, it was a week-long debugging session on a 2000-line ‘Big Ball of Mud’ program I’d written that made me realise that I seriously needed to improve my design skills. Given the lack of useful information in the AppleScript literature, I ended up looking at books, tutorials and examples for Python (useful), Java (not so useful) and Smalltalk (educational), as well as general design books such as Code Complete (a must-have).

Once I’d sort of vaguely grasped the general purpose and motivations for OO design, I was able to go back to the ‘Script Object’ chapter in ASLG and figure out how to translate those principles into AppleScript code. (Which took a little work, as most literature deals with class-based object systems, while AS uses a form of prototype-based OO. But I got there eventually.)

I think the point at which the ‘What is OOP actually useful for?’ question finally clicked was when I wrote my own ‘associative array’ object. As you’ll probably know, AS doesn’t have a built-in hash/dictionary/map/associative array type, something that regularly bugs a lot of users. Storing values under string-based keys was something I was doing quite a bit at the time, creating lists of key-value pairs (e.g. set kvList to {{“some key”, “some value”},…}) to hold the data, and using a bunch of separate to search through those lists to add and retrive values. This all seemed a bit clumsy and disorganised though compared to how the built-in AS types did things, e.g. item 3 of someList is much clearer than getKey(kvList, keyStr). The initial breakthrough was realising that OOP allowed me to make up for AS’s shortage of built-in datatypes by using script objects to define my own ‘datatypes’. This would also give me a much neater syntax closer to that of AS’s own types, e.g. getItem(“foo”) of assocArray.

Once I was able see how OOP could be used to scratch that one particular itch so much better than procedural programming could, the whole concept just started falling into place. Later on, other advantages such as polymorphism also clicked, e.g. for a logging system, I could define interchangeable FileLog, TextEditLog, NullLog objects, each with a different logMessage(str) method. Then, instead of having to put the same big ugly if…elseif…elseif…else… block in my code each time I wanted to log something, I’d just stick one of my logging objects in a global ‘logObj’ variable and have my program say logMessage(…) of logObj. And the ability to group together related code and data into single, self-reliant units made it easier to think about how different parts of my programs could be structured internally, and how they could communicate with one another, resulting in better organised code that was easier to understand and change.

So, my advice is: if you want to learn OOP, first identify an itch that you need to scratch and which sounds like something OOP might be good at scratching (such as a need to supplement AS’s own rather basic datatypes with your own, the need for a flexible, extensible logging system, etc). Next, be prepared to read some general programming literature (especially Code Complete!) and study some other object-oriented languages and code a bit. (You don’t need to get fluent in those other languages or anything, just get familiar enough so you can understand what they’re trying to do and how they’re doing it.) Once you’ve got a rough idea of what OOP’s about, you can come back to the AppleScript literature and code, and start fitting the theory and practice parts together in your head.

One place you will find some decent examples of object-oriented AppleScript code to study is AppleMods. Several of the libraries I wrote for that contain object-oriented code - a good, easy one to start on is the Types library, which includes AssociativeList and Dictionary objects similar to what I’ve described.

HTH, and have fun,

has

Wow! Thanks for that Has. Not only have you terrified me but also whetted my appetite for more (kinda like a slasher -movie junkie ;)) This is very good stuff.

The back of my left hand is now known as the Lost Sea of HHas.

Off to abuse my brain some more…
-Jim

Thanks for the response and references, HHAS, certainly no offence taken by me. You say “most ASers don’t have the time/inclination/resources/need to learn more advanced design skills”, and I would add to that “problem requiring those skills for a reasonable solution”, so your advice to Jim that he should have an “itch” that was “scratchable” using an OOP was solid.

Beginning AppleScripters are often interested in getting something to work and are often not very concerned with programming cleanliness unless their attempts are dog slow. As you point out, my use of script objects is for handling large but AppleScript standard arrays of data; very large chunks of text to be parsed, or lists, records, or lists of lists for examples, and is not OOP at all. Neuburg gives examples of script “Constructors” in his book on AppleScript. They are embedded script objects more closely aligned with your use, and thus closer to the mark.

I think my first introduction to OOP of which my grasp is vague at best, (but then I could say the same about recursive programming - I found LISP impossible to get my head around years ago) was from Matt Neuburg’s book on REALbasic. As is typical of his books, he probed the concepts underlying OOP and I learned a lot about OOP and ignored the details of the Basic language (which I don’t like). Having said that, however, Basic is one of those languages that’s pretty easy to read even if you can’t write it (as is AppleScript), and once you get comfortable with JavaScript’s dot notation, it’s easy to read too. Coming at OOP from the perspective of some other language that genuinly promotes it is good advice. I’ll look into Code Complete.

One of the limitations with using script objects is what goes beyond their creation. Let’s say we create script objects with a constructor function.


set p to {"hello", {1, 2, 3}}
set S to MakeScript(p)
tell S
	repeat with i in num_list of it
		display dialog prompt of it & i
	end repeat
end tell
--
on MakeScript(p)
	set {x, y} to p
	script
		property prompt : x
		property num_list : y
	end script
end MakeScript

If you want to store a lot of script objects and easy access to each one, AppleScript doesn’t provide this, so the best you can do is use what you got. Let’s say we store our script objects in lists.

Anyway I got into another snag here with using script objects trying to work out a good example.

Later,

The best way I’ve found to store script objects is to use a seperate list or text to index the script objects. How can I get the script that has a property "prop’ whose value is “somevalue”. What I do is use a seperate string. Our string is a table of key value pairs mathcing certain script objects.

Let’s say we use the name property of a script object. If you have say one million entries, then you could use blocks of text to hold properties of each script object. Each script object has its own info, but here we’re using text to store information to access a script object. Let’s let each block be twenty bytes. Then, we can use a function to get the index of each script. All you need do is do an integer division by twenty and add one.

Here’s our key value pair, “script1” and the value is the properties of our template script.

So let’s look at our script maker:


property keys:""
property max_key_length: 20

on MakeScript(p)
     set {x, y} to p
     script
         property key : x
         property value : y
     end script
 end MakeScript

Any call to the MakeScript handler will create an instance of our script as defined in the subroutine. Let’s make a script.

set S to MakeScript(“yo”, {1,2,3})
set temp_key to key of S
set l to length of temp_key
set keys to keys & return & “temp_key”

etc. and you can create blocks of keys. Then you would get a key with a little math. Somethning with div 20.

Darn I wish I could make a good example but can’t think of anything right now.

gl,

Not really sure what your code above was trying to do, but if it’s a general-purpose key-value storage structure you’re wanting, AppleMods’ Types library has a couple fairly decent ones. For example, ‘Dictionary’ objects can store any retrieve any sort of value given an arbitrary text key:

set d to _Types's makeDict()

d's setItem("joe", {31, true})
d's setItem("jane", {25, false})
d's setItem("sam", {39, false})

d's getItem("jane")
--> {25, false}

The Dictionary object’s performance is quite decent considering it’s not a built-in type, although adding new items does bog down a bit once you pass the 1000-item mark or thereabouts due to limitations of the internal implementation. (Internally it stores items in sorted lists, so the larger those lists are, the longer it takes AppleScript to to split and rejoin them when adding a new item.) But if that’s an issue, there are plenty other algorithms that are more efficient with larger collections that could easily be implemented in AppleScript. Plus, thanks to wonders of polymorphism, as long as the new objects’ interfaces remain the same, the rest of your code can use them interchangeably regardless of how they work internally.

HTH

has