Creating a script object and storing in a list

Reference http://bbs.applescript.net/viewtopic.php?id=21616 and I think I have done what some of the submissions have suggested and still something is very wrong, so here goes …


property allFiles : {}

on MakeNewFile(forWindow)
	script
		
		property theWindow : forWindow

		-- other properties

		on setName(theName)
			set the associated file name of theWindow to theName
		end setName
		
		-- other handlers
	
	end script
end MakeNewFile

on getFile(withName)
	
	set myFile to null
	
	repeat with f in allFiles
		-- copy (item f of allFiles) to theFile -- soaks up lots of memory
		set theFile to (item f of allFiles) -- ERROR = "Can't get item (item 1 of {<<script>>}) of {<<script>>}"
		set theFileName to the associated file name of (theWindow of theFile)
		if theFileName = withName then
			set myFile to theFile
			exit repeat
		end if
	end repeat
	
	return myFile
	
end getFile

on addWindow(itsFilename) -- parm = a string
	
        -- stuff

	set newFile to MakeNewFile(genericWindow)

	copy newFile to the beginning of allFiles
	-- copy {newFile} to the beginning of allFiles -- have also tried this
	
end addWindow

-- later

set theFile to my getFile(theFileName) -- posts error as marked above

What really is confusing is that Apple’s “Mail Search” code seems to have similar code and it works??

I think you either need to change the repeat loop in your getFile() handler or change what you assign to the variable theFile.

As it is, the the loop variable f is set to (a reference to) each item in allFiles. You do not need to extract the object from the allFiles list again. This is why your item reference form is not working, it is trying to use f (which holds a reference to an item of the list) as an index into the list (which should be an integer). If you are going to be directly comparing the object in the loop variable f to another value, then you might want to dereference it though. That is when you might want to change what you put into theFile: set theFile to contents of f.

Otherwise, if you expect the variable f to hold successive integer indices of the allFiles list, then change the loop to repeat with f from 1 to length of allFiles. If you do this, then your set theFile to (item f of allFiles) line should stay the way it is.

Also, in your addWindow() handler, I think you can get away with set beginning of allFiles to newFile. I have heard that copy is best avoided when dealing with script objects. The MakeNewFile() handler will always return a fresh script object, there should be no reason to copy the object it returns.

That said, I am writing all this from a perspective of no AppleScript Studio experience. So everything I have written here may be wrong for you! Though if I thought that was really the case I would not have posted. The stuff that is giving you your problem is fairly fundamental, so it is the same across “vanilla” AppleScript and Studio augmented AppleScript.

Thanks Chris.

Your point about repeat with f from 1 to length of allFiles is accurate … as a result, lots of errors disappeared.

There has been gobbs of discussion on copy vs set on this BB; but your point about MakeNewFile returning a brand new file seems to make copy (which also makes a new file) the equivalent of making 2 new files which really wastes bandwidth.

The elimination of alot of errors is real … the bad news is that I am picking up a few more … back to the grind … and thanks again.

Chris … Gotta another question …

Apple’s Mail Search program adds a script object to a list this way:

set listObjects to listObjects &  {newObject} -- or, reverse, I guess, for adding to beginning

My question centers on the braces surrounding newObject; with the braces, newObjects is the sole occupant of a new list … in effect, adding a list of 1 to another list, producing a list one member longer. Not having braces seems to work just as well. After all the newObject added is one entity, one object in this case … so what am I missing?

The reason it works like that is due to the “magic” way the concatenation operator (&) is specified. In the case of your code, the left operand is a not a record or a string, so both operands will be coerced to lists. The left operand is always a list, so the coercion has no effect. The right operand is a list when written as {newObject} and not a list when written as newObject. Coercion to list has no effect in the former case. In the latter case, the coercion yields an effective operand of {newObject}. This coercion of the right operand makes both ways of writing it equivalent.

If you plan on putting the new item at the beginning of the list, I would advise always using {newObject} unless you are 100% certain that the value will never be a string or a record. Actually I would argue that one should use the braces to avoid the automatic coercion in all cases, just to make things clearer. Also, consider the situation where the value of newObject is a list itself (listObjects is a to be a list of lists). Then {newObject} is the only way to go.

If one is always adding only a single item to the list one can also take a different approach: set end of listObjects to newObject or set beginning of listObjects to newObject. This way is faster according to posts in this BBS and according to my testing. I would not advocate going out of one’s way to use this method, but it might be important at some point. Usually all the AppleScript code I write is fast enough for my purposes that I never bother to profile or optimize it. I had never bothered to try to measure concatenation versus beginning/end until just now. Basically, use whichever style makes the code easier to read unless the list code is shown to be a bottleneck.

Exercising the concatenation operator a bit:

set listA to {"a", "a"}
set listB to {"b", "b"}
set a to "A"
set b to "B"
script c
end script
set d to 1.61
set e to {name:"record", value:1}
set f to {name:"here", destination:"there"}

(* list to anything
 *	right operand is coerced to a list
 *	if it is already a list, nothing special happens
 * 	if it is not a list, it is converted to a single-item list containing the original operand
 *)
listA & listB --> {"a", "a", "b", "b"}
listA & b --> {"a", "a", "B"}
listB & c --> {"b", "b", «script c»}
listB & d --> {"b", "b", 1.61}
listB & e --> {"b", "b", "record", 1} -- records can be coerced to lists, but the order should not be relied upon

(* string to anything
 *	right operand coerced to a string
 *	some things cannot be coerced to string (in "vanilla" AppleScript; OSAXen can change this)
 *)
b & a --> "BA"
b & listA --> "Baa" -- right operand coerced to string
try
	a & c
on error m number n
	{|ERROR|:{m, n}}
end try
result --> {|ERROR|:{"Can't make «script c» into type string.", -1700}}
a & d --> "A1.61"
try
	a & e
on error m number n
	{|ERROR|:{m, n}}
end try
result --> {|ERROR|:{"Can't make {name:\"record\", value:1} into type string.", -1700}}

(* non-string/non-record to anything
 *	both operands are coerced to a list
 *)
c & a --> {«script c», "A"}
c & listB --> {«script c», "b", "b"}
c & d --> {«script c», 1.61}
c & e --> {«script c», "record", 1}
d & b --> {1.61, "B"}
d & listA --> {1.61, "a", "a"}
d & c --> {1.61, «script c»}
d & e --> {1.61, "record", 1}

(* record to anything
 *	right operand is coerced to record
 *	nothing but a record can be coerced to a record in "vanilla" AppleScript
 *	properties of left record operand takes precedence over right record operand 
 *)
try
	e & a
on error m number n
	{|ERROR|:{m, n}}
end try
result --> {|ERROR|:{"Can't make \"A\" into type record.", -1700}}
try
	e & listB
on error m number n
	{|ERROR|:{m, n}}
end try
result --> {|ERROR|:{"Can't make {\"b\", \"b\"} into type record.", -1700}}
try
	e & c
on error m number n
	{|ERROR|:{m, n}}
end try
result --> {|ERROR|:{"Can't make «script c» into type record.", -1700}}
try
	e & d
on error m number n
	{|ERROR|:{m, n}}
end try
result --> {|ERROR|:{"Can't make 1.61 into type record.", -1700}}
e & f --> {name:"record", value:1, destination:"there"}
f & e --> {name:"here", destination:"there", value:1}

Model: iBook G4 933
AppleScript: 1.10.7
Browser: Safari 3.0.4 (523.12)
Operating System: Mac OS X (10.4)

Chris, I cannot thank you enough.

The big thing that really gelled my insight was type coercion, a very basic Applescript concept … once I saw that, then the curly braces make total sense.

Now, within my current project, I’ve got a speed problem due in large part to alot of repeat loops which I am trying to cut down significantly.

Again, thanks bunches.

Glad I could give you some insight.

If you are building lists in those bottlenecking repeat loops, it sounds like the set end of method might come in handy. You also might try putting your list in a script object’s property (versus a normal variable). It seems counter-intuitive to me, but apparently it can speed up list creation. I hesitate to pass this on though because I can not really explain it and have not bothered to classify the performance changes, I just keep seeing it over and over (here and on Apple’s applescript-users mailing list). It goes something like this:

to doSomething(someList)
	script s
		property l : {}
	end script
	repeat with i in someList
		set newValue to i + 1 -- or whatever
		set end of l of s to newValue
	end repeat
	l of s
end doSomething

I heave read that this is due to a bug in the way lists in plain variable are handled. That reference uses copy, not set. I do not know if that is significant. Sorry!

Speaking of lists, when I stuff a global allFiles (a list of script objects), initialized to {} and later go back to retrieve one of the items(objects) in allFiles for some reason I cannot get any of the properties of the object … as in:


on MakeNewFile(forWindow)
	script
		
		property theWindow : forWindow
		property anotherProperty : false
		-- etc
      
		-- handlers here
   
	end script
end MakeNewFile

on getFile(withName)
	
	set myFile to null
	
	repeat with theFile in allFiles
		set theFileName to the associated file name of (theWindow of theFile) -- << choke
		if theFileName = withName then
			set myFile to theFile
			exit repeat
		end if
	end repeat
	
	return myFile
	
end getFile

set newFile to MakeNewFile(aWindow)
set beginning of allFiles to {newFile}

set aFile to getFile(aName) -- << chokes: "can't get theWindow of {<<script>>}"

Dunno if this is related to to your most recent feedback on putting the list in a script object’s property ?? or what it’s related to ??

I’ve poured over your informative posts as well as Apple’s Mail Search example and I come up empty … I do notice in Mail Search that it accesses the script object’s properties mostly from within handlers captive to the script … and in one case they access one property from outside the script (their controllerForWindow external Handler).

I also found this http://lists.apple.com/archives/applescript-users/2002/Apr/msg00050.html … one difference is that its author initializes some stuff with instantiating the script and returns the script object – he also puts a list inside the script object as you suggested.

Chris, let me pour over this link and see if lightning strikes … will get back to you later, Chris.

I think the problem here is the use of the curly braces with set the beginning of. Take out the braces in the places where you build the list with the beginning/end syntax and see if that error goes away.

The clue here is in the error message. The script object (represented by the text «script») is wrapped in curly braces in the error message. That implies that the items of allFiles are lists of their own (or at least that one prepended in the implicit run handler (i.e. top-level statement) is a list). Using set beginning of allFiles to newFile should clear that up (just be sure to also do it the same way in any other places where allFiles has items prepended/appended).

I probably confused things when I wrote about the set end of and set beginning of forms. Unlike the concatenation operator, they do not do any implicit coercion so they will append/prepend exactly the value provided to the set command. The extra braces are not needed for those forms (unless you want to append/prepend single-item lists). However, unlike the concatenation operator, they cannot be used to append/prepend multiple items to a list in a single command (use a repeat loop if you need the speed of beginning/end mutation but need to add multiple items).

Chris, I can’t thank you enough … errors are disappearing right and left … with & use braces for type coercion, with beginning/end no braces … got it.

When these errors vanished, I re-disovered that associated file name returns POSIX-style, slash-delimited path in spite of the fact that you set it to the short path AND saw this plainly stated in Studio docs … the stuff you forget.

Thanks bunches … again!