Tuesday, January 16, 2018

#1 2017-12-20 12:06:44 am

bmose
Member
From:: Massachusetts
Registered: 2006-01-03
Posts: 231

Conversion of NSSet objects to Applescript lists

Of the three major Cocoa collection classes, NSArray (and its subclasses), NSDictionary (and its subclasses), and NSSet (and its subclasses), only the first two are readily converted to Applescript values with the "as" operator:

Applescript:

set || to current application

set myArray to ||'s NSArray's arrayWithArray:{||'s NSNumber's numberWithInt:1, ||'s NSString's stringWithString:"two", ||'s NSArray's arrayWithArray:{||'s NSNumber's numberWithInt:3, ||'s NSArray's arrayWithArray:{||'s NSNumber's numberWithInt:4, ||'s NSNumber's numberWithInt:5}}}

set myList to myArray as list --> the Applescript list {1, "two", {3, {4, 5}}}, each item of which, including nested list items, is an Applescript value

set myDictionary to ||'s NSDictionary's dictionaryWithDictionary:{a:||'s NSNumber's numberWithInt:1, b:||'s NSString's stringWithString:"two", c:||'s NSDictionary's dictionaryWithDictionary:{d:||'s NSNumber's numberWithInt:3, e:||'s NSNumber's numberWithInt:4, f:||'s NSNumber's numberWithInt:5}}

set myRecord to myDictionary as record --> the Applescript record {a:1, b:"two", c:{d:3, e:{f:4, g:5}}}, each property value of which, including nested property values, is an Applescript value

However, the same does not hold for the conversion of an NSSet to what would seem to be its natural Applescript counterpart, namely a list:

Applescript:

set mySet to ||'s NSSet's setWithArray:{1, "two", 3.3}
set myList to mySet as list --> Applescript list of length 1, whose sole item is the Cocoa NSSet object mySet

It is straightforward to convert an individual NSSet object to an Applescript list by first converting it to an NSArray and then converting the NSArray to an Applescript list:

Applescript:

set mySet to ||'s NSSet's setWithArray:{1, "two", 3.3}
set myArray to mySet's allObjects()
set myList to myArray as list --> Applescript list of length 3, whose items are the Applescript values 1, "two", and 3.3 in an undefined order

However, this approach can't be applied globally to NSSet objects nested within other Cocoa collections (NSArray, NSDictionary, and NSSet) or Applescript lists or records without applying the conversion technique to each NSSet object individually.

The cocoaToASValue handler described below is an ASObjcC handler that attempts to overcome this missing functionality. It is a general purpose Cocoa object-to-Applescript value converter that converts NSSet (or NSSet subclass) objects to Applescript lists, including NSSet objects nested within Cocoa collections (NSArray, NSDictionary, and NSSet objects) and Applescript lists and records.

For those not accustomed to using NSSet objects, a couple of things to keep in mind concerning the Applescript lists to which they are converted:
(1) Since NSSet objects are unordered, the order of the Applescript list items after conversion will be undefined
(2) (This actually pertains to the conversion of an Applescript list to an NSSet, not the conversion of an NSSet to an Applescript list for which the current handler is designed. But I still think it's worth mentioning in this discussion of NSSet objects and Applescript lists.) Since NSSet objects (apart from NSCountedSet objects) do not allow duplicate values, any items that are distinct from Applescript's viewpoint but identical from Cocoa's viewpoint will be reduced to a single value in the NSSet as well as the Applescript list following conversion. For example, each of the following NSSet instantiation commands will result in a single-item NSSet object and consequently a single-item Applescript list after conversion:

Applescript:

(current application's NSSet)'s setWithArray:{1, true} --> the Applescript list {1} after conversion
(current application's NSSet)'s setWithArray:{0, false} --> the Applescript list {0} after conversion.

Example of handler usage:

Applescript:

set || to current application
set cocoaArray to (||'s NSArray)'s arrayWithArray:{1, 2.2, "three"}
set cocoaDictionary to (||'s NSDictionary)'s dictionaryWithDictionary:{a:false, b:{4, 5, 6}, c:{aa:7, bb:8, cc:9}}
set cocoaSet to (||'s NSSet)'s setWithArray:{10, 11.11, "twelve"}
set cocoaArrayWithNestedObjects to (||'s NSArray)'s arrayWithArray:{cocoaArray, cocoaDictionary, cocoaSet, {cocoaArray, cocoaDictionary, cocoaSet, {cocoaArray, cocoaDictionary, cocoaSet, {cocoaArray, cocoaDictionary, cocoaSet}}}}

my cocoaToASValue(cocoaArrayWithNestedObjects)
--> {{1, 2.200000047684, "three"}, {a:false, b:{4, 5, 6}, c:{aa:7, bb:8, cc:9}}, {11.109999656677, 10, "twelve"}, {{1, 2.200000047684, "three"}, {a:false, b:{4, 5, 6}, c:{aa:7, bb:8, cc:9}}, {11.109999656677, 10, "twelve"}, {{1, 2.200000047684, "three"}, {a:false, b:{4, 5, 6}, c:{aa:7, bb:8, cc:9}}, {11.109999656677, 10, "twelve"}, {{1, 2.200000047684, "three"}, {a:false, b:{4, 5, 6}, c:{aa:7, bb:8, cc:9}}, {11.109999656677, 10, "twelve"}}}}}
--> note the apparent alteration in the order of NSSet items following conversion to Applescript lists

Handler:

Applescript:

use framework "Foundation"
property || : current application

on cocoaToASValue(theObj)
   -- Converts a Cocoa object to a corresponding Applescript value
   -- If it cannot be converted, it will remain unchanged as a Cocoa object
   tell theObj
       -- Perform a preliminary conversion of the input argument to an Applescript value
       -- If the input argument is a collection, extract its item values into an Applescript list
       set dictionaryKeys to null
       try
           -- Handle the case where the input argument is a Cocoa object
           -- Note that if the input argument is instead an Applescript value, it will trigger an error when it encounters the isKindOfClass method call and will be processed in the "on error" clause
           if (its isKindOfClass:((my ||'s NSArray)'s |class|())) as boolean then
               -- If the input argument is a Cocoa NSArray or one of its subclasses, convert it to an Applescript list
               set asValue to it as list
           else if (its isKindOfClass:((my ||'s NSSet)'s |class|())) as boolean then
               -- If the input argument is a Cocoa NSSet or one of its subclasses, first convert it to an NSArray, then convert the NSArray to an Applescript list
               -- Note that since an NSSet is unordered, the converted list items may be in any order
               set asValue to (its allObjects()) as list
           else if (its isKindOfClass:((my ||'s NSDictionary)'s |class|())) as boolean then
               -- If the input argument is a Cocoa NSDictionary or one of its subclasses, extract its keys as an NSArray and its values as an Applescript list
               set dictionaryKeys to its allKeys()
               set asValue to (its objectsForKeys:dictionaryKeys notFoundMarker:(null)) as list
           else
               -- Otherwise, get the Cocoa object's Applescript value by first making it the single item of a list, then extracting the list's item, which the Cocoa-Applescript bridge will convert to an Applescript value if the conversion is possible (if the conversion is not possible, the item will remain the original Cocoa object)
               set asValue to (it as list)'s first item
           end if
       on error
           -- Handle the case where the input argument is an Applescript value
           if its class = record then
               -- If the input argument is an Applescript record, extract its keys as an NSArray and its values as an Applescript list
               tell ((my ||'s NSDictionary)'s dictionaryWithDictionary:it)
                   set dictionaryKeys to its allKeys()
                   set asValue to (its objectsForKeys:dictionaryKeys notFoundMarker:(null)) as list
               end tell
           else
               -- Otherwise, get the input argument's Applescript value
               set asValue to it
           end if
       end try
   end tell
   -- If the input argument was a Cocoa or Applescript collection, convert its extracted objects/values to Applescript values in a recursive fashion so that nested collections are converted properly
   tell asValue
       if its class = list then
           -- Convert the extracted objects/values recursively
           set asValue to {}
           repeat with i in it
               set end of asValue to my cocoaToASValue(i's contents)
           end repeat
           -- If the input argument was a Cocoa NSDictionary (or one of its subclasses) or an Applescript record, reconstruct it as an Applescript record from its extracted keys and converted values
           if dictionaryKeys ≠ null then set asValue to ((my ||'s NSDictionary)'s dictionaryWithObjects:((my ||'s NSArray)'s arrayWithArray:asValue) forKeys:dictionaryKeys) as record
       end if
   end tell
   -- Return the input argument's Applescript value
   return asValue
end cocoaToASValue

Last edited by bmose (2017-12-20 11:37:05 am)


Filed under: list, conversion, NSSet

Offline

 

#2 2017-12-20 12:29:30 am

Shane Stanley
Member
From:: Australia
Registered: 2002-12-07
Posts: 5230

Re: Conversion of NSSet objects to Applescript lists

Since NSSet objects are unordered, the Applescript lists after conversion will also be unordered



I'm not sure that says quite what you mean -- the AppleScript list will still be ordered.

Also, you assuming that allKeys and allValues return their results in the same order, but you can't make that assumption. The documentation for both specifically makes the point that the order isn't defined. if you want matching arrays, you need to get the keys using allKeys, and then use the result in -objectsForKeys:notFoundMarker: to get the elements in the same order.


Shane Stanley <sstanley@myriad-com.com.au>
www.macosxautomation.com/applescript/apps/

Offline

 

#3 2017-12-20 12:46:49 am

Shane Stanley
Member
From:: Australia
Registered: 2002-12-07
Posts: 5230

Re: Conversion of NSSet objects to Applescript lists

Since NSSet objects (apart from NSCountedSet objects) do not allow duplicate values, any items in the NSSet that are of identical value



By definition, that can never happen -- you can't have two items of identical value in a set. You give the example of "integer 1 and the boolean value true", but to be added to a collection in the first place they must be boxed as NSNumbers, and at that point they are equal and therefore duplicates. For example:

Applescript:

use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use scripting additions

set x to current application's NSMutableArray's array()
x's addObject:(current application's NSNumber's numberWithBool:true)
x's addObject:(current application's NSNumber's numberWithInteger:1)
x's addObject:(current application's NSNumber's numberWithDouble:1.0)
set a to (current application's NSSet's setWithArray:x)'s |count|()


Shane Stanley <sstanley@myriad-com.com.au>
www.macosxautomation.com/applescript/apps/

Offline

 

#4 2017-12-20 12:47:40 am

bmose
Member
From:: Massachusetts
Registered: 2006-01-03
Posts: 231

Re: Conversion of NSSet objects to Applescript lists

Shane Stanley wrote:

I'm not sure that says quite what you mean -- the AppleScript list will still be ordered.

Good point. I modified the wording to indicate that the Applescript list item order will be undefined after conversion from an NSSet.

Shane Stanley wrote:

you need to get the keys using allKeys, and then use the result in -objectsForKeys:notFoundMarker: to get the elements in the same order

Thank you for pointing that important point out. I modified the code to incorporate that method call to properly match NSDictionary keys and values.

Offline

 

#5 2017-12-20 01:06:25 am

bmose
Member
From:: Massachusetts
Registered: 2006-01-03
Posts: 231

Re: Conversion of NSSet objects to Applescript lists

Shane Stanley wrote:

By definition, that can never happen -- you can't have two items of identical value in a set. You give the example of "integer 1 and the boolean value true"

Thank you again for pointing out my sloppy wording. I was of course referring to the case where an NSSet is instantiated with Applescript values that are distinct from Applescript's viewpoint but are identical from Cocoa's viewpoint. For example, each of the following NSSet objects will have only a single item because the distinct Applescript values will be seen as identical in Cocoa:

Applescript:

current application's NSSet's setWithArray:{1, true}
--and--
current application's NSSet's setWithArray:{0, false}

I tidied up the wording in the original post.

Last edited by bmose (2017-12-20 01:15:44 am)

Offline

 

#6 2017-12-20 02:45:04 am

Shane Stanley
Member
From:: Australia
Registered: 2002-12-07
Posts: 5230

Re: Conversion of NSSet objects to Applescript lists

bmose wrote:

I tidied up the wording in the original post.



I'm still scratching my head over it. The handler deals with Cocoa objects that might include sets. By definition, those sets contain any item only once. So what you're saying might be true, but it's irrelevant because the sets are, well, what they already are -- the number of items in the resulting list will always match the number in the set it was converted from.

I guess what I'm saying is that I don't think you need either of your caveats.


Shane Stanley <sstanley@myriad-com.com.au>
www.macosxautomation.com/applescript/apps/

Offline

 

#7 2017-12-20 06:52:55 am

bmose
Member
From:: Massachusetts
Registered: 2006-01-03
Posts: 231

Re: Conversion of NSSet objects to Applescript lists

Shane Stanley wrote:

I guess what I'm saying is that I don't think you need either of your caveats.

I was at first taken by surprise during development of the handler when a sample NSSet initialized with items that included the Applescript integer 1 and the Applescript boolean true value ended up with one less Applescript list item after conversion than I was expecting. Then the light bulb went off that Cocoa sees those two distinct Applescript values as one identical value and thus includes it only once in the set. My intention was to help keep others, especially those new to using NSSet objects, from scratching their heads trying to understand the Applescript lists to which they are converted. I replaced the word "caveat" with something that conveys my intention better.

Last edited by bmose (2017-12-20 06:53:46 am)

Offline

 

#8 2017-12-20 07:09:44 am

Shane Stanley
Member
From:: Australia
Registered: 2002-12-07
Posts: 5230

Re: Conversion of NSSet objects to Applescript lists

bmose wrote:

I replaced the word "caveat" with something that conveys my intention better.



The intention is good -- I was just concerned about confusion, because the issues only affect going from list to set, whereas you handler is all about going the other way. If you have a list like {true, 1}, there's nothing to convert smile


Shane Stanley <sstanley@myriad-com.com.au>
www.macosxautomation.com/applescript/apps/

Offline

 

#9 2017-12-20 07:31:46 am

bmose
Member
From:: Massachusetts
Registered: 2006-01-03
Posts: 231

Re: Conversion of NSSet objects to Applescript lists

Shane Stanley wrote:

the issues only affect going from list to set, whereas you handler is all about going the other way

Another good point.  I made the distinction clearer in the post.

One other point: Since the handler is specifically geared toward converting Cocoa objects to Applescript values, I changed the name of the handler to cocoaToASValue smile

Last edited by bmose (2017-12-20 07:48:03 am)

Offline

 

#10 2017-12-20 03:30:11 pm

Nigel Garvey
Moderator
From:: Warwickshire, England
Registered: 2002-11-20
Posts: 4486

Re: Conversion of NSSet objects to Applescript lists

Nice one!  cool

I've been trying to improve on it all day to no avail.  wink  My own effort's somewhat more compact, but only just over half as fast:

Applescript:

use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"

property || : a reference to current application

on convertToASValue(theObj)
   -- An internal script object containing a recursive handler.
   script o
       on itemiseSetsIn(theObj)
           -- If theObj is a set or an ordered set, get an array version of it.
           if (theObj's isKindOfClass:(||'s class "NSSet")) as boolean then
               set theObj to theObj's allObjects()
           else if (theObj's isKindOfClass:(||'s class "NSOrderedSet")) as boolean then
               set theObj to theObj's array()
           end if
           -- If what we have is an array or a dictionary, get a mutable version of it and recursively edit its items.
           -- It's irritating that mutable copies have to be made and the items set even though nothing may actually need changing, but it takes just as long to find out beforehand if it's necessary!
           if (theObj's isKindOfClass:(||'s class "NSArray")) as boolean then
               set theObj to theObj's mutableCopy()
               repeat with i from 1 to (count theObj)
                   set item i of theObj to itemiseSetsIn(item i of theObj)
               end repeat
           else if (theObj's isKindOfClass:(||'s class "NSDictionary")) as boolean then
               set theObj to theObj's mutableCopy()
               set theKeys to theObj's allKeys()
               repeat with thisKey in theKeys
                   (theObj's setObject:(itemiseSetsIn(theObj's objectForKey:(thisKey))) forKey:(thisKey))
               end repeat
           end if
           
           -- Return the edited object to the recursion level above,
           return theObj
       end itemiseSetsIn
   end script
   
   -- Call the recursive handler to exchange any sets or ordered sets for arrays.
   set convertedObj to o's itemiseSetsIn(theObj)
   -- Get an AS version of the result by coercing an array containing it to list and extracting the only item.
   return ((||'s class "NSArray"'s arrayWithObject:(convertedObj)) as list)'s beginning
end convertToASValue


NG

Offline

 

#11 2017-12-21 02:48:41 pm

bmose
Member
From:: Massachusetts
Registered: 2006-01-03
Posts: 231

Re: Conversion of NSSet objects to Applescript lists

Nigel,

Thank you for your kind words.

I like your direct reference to Cocoa classes (e.g., class "NSSet") rather than my method-based expression (e.g., NSSet's |class|()) when testing for class type.  I also appreciate your adding the Cocoa NSOrderedSet collection class that I neglected to include.  I incorporated both features, along with some minor coding and wording improvements, into the script below that I consider to be the preferred version over that which was originally submitted.

Inspired by your effort, I also tried extracting collection objects and values into Cocoa NSArray objects rather than into Applescript lists (as is currently the case in my script) before processing them recursively in the repeat loop.  It eliminates the initial conversion step for an input argument that is a Cocoa object, but it adds a new conversion step for an input argument that is an Applescript value.  Alas, I too found an execution speed hit, with my modified version executing at only 40% of the speed of the current version. Thus, I abandoned that approach.

I would like to ask a couple of questions:

(1) Why do you use the expression a reference to current application rather than simply current application in your property statement?
(2) Towards the end of your script, you use the expression ||'s class "NSArray"'s arrayWithObject:.... Is there an advantage of that form over ||'s NSArray's arrayWithObject:..., which seems simpler?
(3) This is a rhetorical question. How did it take me so many years to learn that one can use beginning as an alternative to first item or item 1, and end as an alternative to last item or item -1, when referencing the first or last item of a list? hmm I like it and find myself gravitating toward using end to retrieve the sole value of a single-item list to avoid having to type the longer word beginning. But I also find the terminology a bit confusing:

Applescript:

set x to {1, 2, 3}
set y to {1, 2, 3}

get item 1 of x --> 1
get beginning of y --> 1

but...

set item 1 of x to 4 --> {4, 2, 3}
set beginning of y to 4 --> {4, 1, 2, 3}

It is because of the insertion rather than the replacement effect of the term beginning in the final statement that I always thought of it as meaning the location before a list's first item rather than the first item itself.

In any case, here is an example of usage of the improved version of the handler with the addition of NSOrderedSet objects among the nested values:

Applescript:

set cocoaArray to (my ||'s NSArray)'s arrayWithArray:{1, 2.2, "three"}
set cocoaDictionary to (my ||'s NSDictionary)'s dictionaryWithDictionary:{a:false, b:{4, 5, 6}, c:{aa:7, bb:8, cc:9}}
set cocoaSet to (my ||'s NSSet)'s setWithArray:{10, 11.11, "twelve"}
set cocoaOrderedSet to (my ||'s NSOrderedSet)'s orderedSetWithArray:{97, 98.98, "ninety-nine"}
set cocoaArrayWithNestedObjects to (my ||'s NSArray)'s arrayWithArray:{cocoaArray, cocoaDictionary, cocoaSet, cocoaOrderedSet, {cocoaArray, cocoaDictionary, cocoaSet, cocoaOrderedSet, {cocoaArray, cocoaDictionary, cocoaSet, cocoaOrderedSet, {cocoaArray, cocoaDictionary, cocoaSet, cocoaOrderedSet}}}}

my cocoaToASValue(cocoaArrayWithNestedObjects)

--> {{1, 2.200000047684, "three"}, {a:false, b:{4, 5, 6}, c:{aa:7, bb:8, cc:9}}, {11.109999656677, 10, "twelve"}, {97, 98.980003356934, "ninety-nine"}, {{1, 2.200000047684, "three"}, {a:false, b:{4, 5, 6}, c:{aa:7, bb:8, cc:9}}, {11.109999656677, 10, "twelve"}, {97, 98.980003356934, "ninety-nine"}, {{1, 2.200000047684, "three"}, {a:false, b:{4, 5, 6}, c:{aa:7, bb:8, cc:9}}, {11.109999656677, 10, "twelve"}, {97, 98.980003356934, "ninety-nine"}, {{1, 2.200000047684, "three"}, {a:false, b:{4, 5, 6}, c:{aa:7, bb:8, cc:9}}, {11.109999656677, 10, "twelve"}, {97, 98.980003356934, "ninety-nine"}}}}}

And here is the improved version of the handler:

Applescript:

use framework "Foundation"
property || : current application

on cocoaToASValue(theObj)
   -- Converts a Cocoa object to a corresponding Applescript value
   -- If the Cocoa object can't be converted, or if the input argument is an Applescript value, returns the input argument unchanged
   set {tmpList, tmpKeys} to {null, null}
   -- If the input argument is a Cocoa or Applescript collection, extract its item values into an Applescript list; otherwise, set the return value to its Applescript value
   tell theObj
       try
           -- Handle the case where the input argument is a Cocoa object
           -- If the input argument is instead an Applescript value, it will trigger an error when it encounters the isKindOfClass method call and will be processed in the "on error" clause
           if (its isKindOfClass:(my ||'s class "NSArray")) as boolean then
               -- If the input argument is a Cocoa NSArray or one of its subclasses, convert it to a temporary Applescript list
               set tmpList to it as list
           else if (its isKindOfClass:(my ||'s class "NSSet")) as boolean then
               -- If the input argument is a Cocoa NSSet or one of its subclasses, first convert it to an NSArray, then convert the NSArray to a temporary Applescript list
               -- Note that since an NSSet is unordered, the order of the Applescript list items will be undefined
               set tmpList to (its allObjects()) as list
           else if (its isKindOfClass:(my ||'s class "NSOrderedSet")) as boolean then
               -- If the input argument is a Cocoa NSOrderedSet or one of its subclasses, first convert it to an NSArray, then convert the NSArray to a temporary Applescript list
               set tmpList to (its array()) as list
           else if (its isKindOfClass:(my ||'s class "NSDictionary")) as boolean then
               -- If the input argument is a Cocoa NSDictionary or one of its subclasses, extract its keys into an NSArray and its values into a temporary Applescript list
               set tmpKeys to its allKeys()
               set tmpList to (its objectsForKeys:tmpKeys notFoundMarker:(null)) as list
           else
               -- Otherwise, set the return value to its Applescript value via the Cocoa-Applescript bridge by making it the item of a single-item Applescript list, then extracting the list's item
               set asValue to (it as list)'s end
           end if
       on error
           -- Handle the case where the input argument is an Applescript value
           if its class = list then
               -- If the input argument is an Applescript list, assign its value to a temporary Applescript list
               set tmpList to it
           else if its class = record then
               -- If the input argument is an Applescript record, extract its keys into an NSArray and its values into a temporary Applescript list
               tell ((my ||'s NSDictionary)'s dictionaryWithDictionary:it)
                   set tmpKeys to its allKeys()
                   set tmpList to (its objectsForKeys:tmpKeys notFoundMarker:(null)) as list
               end tell
           else
               -- Otherwise, if the input argument is of any other Applescript class, set the return value to it
               set asValue to it
           end if
       end try
   end tell
   -- If the input argument was a Cocoa or Applescript collection, convert its extracted objects/values to Applescript values in a recursive fashion so that nested collections are converted properly
   if tmpList ≠ null then
       -- Convert the extracted objects/values recursively
       set asValue to {}
       repeat with i in tmpList
           set end of asValue to my cocoaToASValue(i's contents)
       end repeat
       -- If the input argument was a Cocoa NSDictionary (or one of its subclasses) or an Applescript record, reconstruct it as an Applescript record from its extracted keys and converted values
       if tmpKeys ≠ null then set asValue to ((my ||'s NSDictionary)'s dictionaryWithObjects:((my ||'s NSArray)'s arrayWithArray:asValue) forKeys:tmpKeys) as record
   end if
   -- Return the input argument's Applescript value
   return asValue
end cocoaToASValue

Last edited by bmose (2017-12-21 04:40:33 pm)

Offline

 

#12 2017-12-21 08:08:44 pm

Nigel Garvey
Moderator
From:: Warwickshire, England
Registered: 2002-11-20
Posts: 4486

Re: Conversion of NSSet objects to Applescript lists

bmose wrote:

I also tried extracting collection objects and values into Cocoa NSArray objects rather than into Applescript lists …  Alas, I too found an execution speed hit, with my modified version executing at only 40% of the speed of the current version.


I think that's why I couldn't make my script as fast as yours. Using some ObjC classes in scripts isn't as fast as using their AS counterparts. Rather counterintuitive.  hmm

(1) Why do you use the expression a reference to current application rather than simply current application in your property statement?


Hedging my bets, mainly. wink  I'm not sure what's compiled into the script with just current application. Is it a general reference to a current application or a specific reference to the one in which the script's compiled? Querying the property in either form returns the keyword current application, so perhaps a reference to isn't necessary.

(2) Towards the end of your script, you use the expression ||'s class "NSArray"'s arrayWithObject:.... Is there an advantage of that form over ||'s NSArray's arrayWithObject:..., which seems simpler?


W-e-l-l… Not really. When I was learning ASObjC from Shane's book and trying out the examples in Script Editor, I found my brain kept rebelling and switching off because there was so much I didn't like about the monolithic appearance of the code on the screen. On the basis that the best way to learn anything is to make it your own, I spent a couple of days thinking how I might do this with ASObjC and eventually decided that for optimum clarity, narrative, and consistency in my own scripts, I'd depart from Shane's style in a number of ways:

• Using a glyph variable instead of congesting the screen with 'current application's.
• Specifying classes as the keyword 'class' followed by the class name as text. Shane's book gives this as one way of getting round a clash between NSURL and a similar keyword in one of the Satimage OSAXen. My logic is that doing it this way every time means that NSURL doesn't have to be special-cased and classes stand out from methods on the page. (ASObjCExplorer and Script Debugger have a facility for changing the colour of method keywords to make them stand out from class keywords, but this isn't much help in Script Editor or on scripting fora. It also changes the colour of handler labels (except for handlers with labelled parameters), but it's not too irritating if you choose the right colour.)
• Parenthesising all values passed to methods, not just those that won't compile otherwise, so that they break up the monotony and are easily visible.
• Using 'tell' (unless it makes more sense not to) where 'set' can't be, in order to convey the impression of something happening.
• Not using interleaved syntax for AppleScript handlers unless it's absolutely unavoidable.

These are just my personal preferences, of course, based on my own ideas of clarity and how AS and ASObjC should coexist in a script. (And I did find ASObjC more interesting and easier to learn after adopting them!  wink)  But if I'm commenting on or further developing someone else's script, I'll generally try to adopt their style — both out of politeness and so that they can follow the changes.

(3) This is a rhetorical question.


OK. I won't answer it.  wink  beginning and end are properties of a list, which may explain why getting them is minusculely faster than getting the elements item 1 and item -1.

I'll try out your improved script in the morning.  smile

Last edited by Nigel Garvey (2017-12-21 08:10:51 pm)


NG

Offline

 

#13 2017-12-21 10:42:07 pm

bmose
Member
From:: Massachusetts
Registered: 2006-01-03
Posts: 231

Re: Conversion of NSSet objects to Applescript lists

Nigel,

Thanks again for all the helpful pieces of information.

I can especially relate to your willingness to be a bit verbose if it promotes consistency and clarity.  For example, I use tell blocks that might otherwise be expressed in fewer lines without the block for the validation of a handler's input variables, where I delineate each variable's validation code within a tell [variable name]...end tell block whenever feasible.  I find it to be a great organizational tool that promotes clarity.  Another mildly verbose habit I've developed that I find extremely helpful is to use an Applescript record as the single input parameter and single output parameter for virtually all my handlers.  (I break the rule when posting online because positional parameters seem to be so much more commonly used.)  The use of a single record is not only consistent across all handlers, but the record property labels afford the opportunity to give expressive names to each input and output property reminiscent of the expressive names used for Cocoa entities.  Another benefit is the ease with which default values can be supplied to individual input properties by simply concatenating a record with the default values to the end of the input record. Yet another benefit is that an arbitrary number of input or output properties may be specified, including zero properties by coding the record as {} (i.e., the empty list/record.)

One final thing that I'm sure you've noticed...I quickly adopted your use of the glyph variable || in place of current application in my ASObjC code.  It's so-o-o-o much easier to both read and write!

Last edited by bmose (2017-12-26 11:21:32 am)

Offline

 

#14 2017-12-22 01:20:33 am

Shane Stanley
Member
From:: Australia
Registered: 2002-12-07
Posts: 5230

Re: Conversion of NSSet objects to Applescript lists

FWIW, I did ask one of the then AppleScript engineers some time ago whether there was any difference between referring to classes by name or a variable. The answer was that by variable was now the "preferred" way, but I couldn't get any more response than that. I suspect it makes little difference, although using names initially caused some problems because the AppleScript compiler had never accepted named classes -- editors have to include some vaguely hackish code to get it to compile.

The issue of current application is a vexed question. I initially tried using other variables, and even having it so they didn't actually appear in code. I also pleaded for alternatives that were at least shorter and one word, obviously to no avail. My impression was there there was a hope that they could eventually be made unnecessary, but that may have just been my wishful thinking.

My ultimate preference is to define them in properties, but that doesn't translate to places like this very well. But especially in the case of enums, it is much safer because so many enums have been renamed in recent releases.

Wearing my Script Debugger hat, the || approach doesn't play well with code-completion. If you're not using Script Debugger, or you simply don't care for code-completion, it doesn't matter. I toyed with the idea of supporting it as an alternative, but the choice of a non-ASCII character helped dissuade me for technical reasons. It's also a bit of a slippery slope because I've seen others preferring different schemes.

My feeling is that whoever supplies the code gets to use their preference. But I also think there's something to be said, at least in places like this particular section, to keeping things reasonably vanilla unless there are reasons of performance involved.


Shane Stanley <sstanley@myriad-com.com.au>
www.macosxautomation.com/applescript/apps/

Offline

 

#15 2017-12-22 04:24:35 am

Nigel Garvey
Moderator
From:: Warwickshire, England
Registered: 2002-11-20
Posts: 4486

Re: Conversion of NSSet objects to Applescript lists

bmose wrote:

I like your direct reference to Cocoa classes (e.g., class "NSSet") rather than my method-based expression (e.g., NSSet's |class|()) when testing for class type.


That's using my "style", of course. If you prefer the variable syntax, it would just be NSSet, as in:

Applescript:

else if (its isKindOfClass:(||'s NSSet)) as boolean then

And here is the improved version of the handler:


I haven't been able to catch it out so far.  smile  To make things as pathological as possible, I set the NSOrderedSet first, made it an additional member of the NSSet, and made both additional values in the NSDictionary. Everything was correctly rendered.  smile

Last edited by Nigel Garvey (2017-12-22 05:55:58 am)


NG

Offline

 

#16 2017-12-22 04:26:23 am

Nigel Garvey
Moderator
From:: Warwickshire, England
Registered: 2002-11-20
Posts: 4486

Re: Conversion of NSSet objects to Applescript lists

"Shane Stanley" wrote:

Wearing my Script Debugger hat, the || approach doesn't play well with code-completion.


I did notice that.  smile

If you're not using Script Debugger, or you simply don't care for code-completion, it doesn't matter.


I'm not a great fan of text completion generally, although I find it quite handy in Numbers. Obviously I don't use it in Script Debugger, but I do use text substitutions to mitigate the use of || (or at the moment |⌘|) and the class "name" syntax and could theoretically use it to enter commonly used methods whose use I didn't have to look up anyway.


NG

Offline

 

#17 2017-12-22 05:32:50 am

bmose
Member
From:: Massachusetts
Registered: 2006-01-03
Posts: 231

Re: Conversion of NSSet objects to Applescript lists

Nigel Garvey wrote:

To make things as pathological as possible, I set the NSOrderedSet first, made it an additional member of the NSSet, and made both additional values in the NSDictionary. Everything was correctly rendered.

That's good feedback to get.  While the handler started out as a means of converting Cocoa NSSet's to Applescript lists, it quickly evolved into a what I hope is a general purpose Cocoa object-to-Applescript value converter.

Last edited by bmose (2017-12-24 08:27:12 am)

Offline

 

#18 2017-12-22 05:47:20 am

Shane Stanley
Member
From:: Australia
Registered: 2002-12-07
Posts: 5230

Re: Conversion of NSSet objects to Applescript lists

Nigel Garvey wrote:

I'm not a great fan of text completion generally



I wasn't either, until I started with Objective-C. Those long names and case-sensitivity made me a convert.


Shane Stanley <sstanley@myriad-com.com.au>
www.macosxautomation.com/applescript/apps/

Offline

 

#19 2017-12-22 05:59:24 am

Nigel Garvey
Moderator
From:: Warwickshire, England
Registered: 2002-11-20
Posts: 4486

Re: Conversion of NSSet objects to Applescript lists

I wrote:

And here is the improved version of the handler:


I haven't been able to catch it out so far.  smile  To make things as pathological as possible, I set the NSOrderedSet first, made it an additional member of the NSSet, and made both additional values in the NSDictionary. Everything was correctly rendered.  smile


PS.

bmose wrote:

Applescript:

--> {{1, 2.200000047684, "three"}, {a:false, b:{4, 5, 6}, c:{aa:7, bb:8, cc:9}}, {11.109999656677, 10, "twelve"}, {97, 98.980003356934, "ninety-nine"}, {{1, 2.200000047684, "three"}, {a:false, b:{4, 5, 6}, c:{aa:7, bb:8, cc:9}}, {11.109999656677, 10, "twelve"}, {97, 98.980003356934, "ninety-nine"}, {{1, 2.200000047684, "three"}, {a:false, b:{4, 5, 6}, c:{aa:7, bb:8, cc:9}}, {11.109999656677, 10, "twelve"}, {97, 98.980003356934, "ninety-nine"}, {{1, 2.200000047684, "three"}, {a:false, b:{4, 5, 6}, c:{aa:7, bb:8, cc:9}}, {11.109999656677, 10, "twelve"}, {97, 98.980003356934, "ninety-nine"}}}}}


It doesn't give me those floating-point inaccuracies in the results:

Applescript:

--> {{1, 2.2, "three"}, {a:false, b:{4, 5, 6}, c:{bb:8, cc:9, aa:7}}, {"twelve", 10, 11.11}, {97, 98.98, "ninety-nine"}, {{1, 2.2, "three"}, {a:false, b:{4, 5, 6}, c:{bb:8, cc:9, aa:7}}, {"twelve", 10, 11.11}, {97, 98.98, "ninety-nine"}, {{1, 2.2, "three"}, {a:false, b:{4, 5, 6}, c:{bb:8, cc:9, aa:7}}, {"twelve", 10, 11.11}, {97, 98.98, "ninety-nine"}, {{1, 2.2, "three"}, {a:false, b:{4, 5, 6}, c:{bb:8, cc:9, aa:7}}, {"twelve", 10, 11.11}, {97, 98.98, "ninety-nine"}}}}}

Last edited by Nigel Garvey (2017-12-22 06:08:34 am)


NG

Offline

 

#20 2017-12-22 06:07:23 am

Shane Stanley
Member
From:: Australia
Registered: 2002-12-07
Posts: 5230

Re: Conversion of NSSet objects to Applescript lists

Nigel Garvey wrote:

It doesn't give me those floating-point inaccurancies in the results:



That happens before 10.11 -- doubles get converted to floats, losing precision. Of course several other classes are also  not converted pre-10.11.


Shane Stanley <sstanley@myriad-com.com.au>
www.macosxautomation.com/applescript/apps/

Offline

 

#21 2017-12-22 06:16:19 am

bmose
Member
From:: Massachusetts
Registered: 2006-01-03
Posts: 231

Re: Conversion of NSSet objects to Applescript lists

Shane Stanley wrote:

That happens before 10.11 -- doubles get converted to floats, losing precision. Of course several other classes are also  not converted pre-10.11.

It's time for me to get up with the times. I dread the tweaks that will be necessary in the 100+ utility handlers that are at the core of my automations. But when it's over, I'm sure it will be delightful.

Offline

 

#22 2017-12-22 06:38:24 am

Shane Stanley
Member
From:: Australia
Registered: 2002-12-07
Posts: 5230

Re: Conversion of NSSet objects to Applescript lists

bmose wrote:

It's time for me to get up with the times. I dread the tweaks that will be necessary in the 100+ utility handlers that are at the core of my automations. But when it's over, I'm sure it will be delightful.



From an ASObjC point of view 10.11 or later is more reliable, and the bridging of dates is a great leap forward.


Shane Stanley <sstanley@myriad-com.com.au>
www.macosxautomation.com/applescript/apps/

Offline

 

Board footer

Powered by FluxBB

RSS (new topics) RSS (active topics)