Anonymous Script Objects
Technically, you can have anonymous script objects, though they can not be “inline” since script is a statement not an expression. So they are still not as nice as Perl’s blocks-as-coderefs or even its anonymous subs.
script
on lambda(someone)
-- .
end lambda
end script
grep over {"Mark", "Erin", "David"} given script:result
Accordingly,
script foo
.
end
is almost exactly the same as
script
.
end
set foo to result
List Accumulation
For list accumulation, the common AppleScript idiom goes like this:
set newList to {}
.
set end of newList to aValue
This directly appends to the list instead of having to create a whole new copy as you are doing with the list concatenation operator.
For performance reasons, sometimes this is written in a slight obtuse manner:
script helper
property newList : {}
end
.
set end of helper's newList to aValue
They do the same thing, but the second version is more efficient due to the way AppleScript is implemented (see an AppleScript-users email by Nigel Garvey on the “Serge method”).
Limiting the Scope of Tell Blocks
In your grep handler, you have more code in the tell block than is strictly necessary. Actually, as in your map handler, the tell block is not needed at all. The is true is only useful for avoiding a run-time error in the case where lambda returns a non-boolean value. It seems to me that it is likely to be a bug to pass such a handler to your grep, so I would skip is true and let the run-time error happen.
Implicit References with “repeat with X in” Loops
As you may have noticed, using repeat with X in someListValue causes X to be a reference to the item in someListValue instead of the value itself. You can see the difference in the following example:
to noderef()
repeat with x in {"A"}
return x
end repeat
end noderef
to deref()
repeat with x in {"A"}
return contents of x
end repeat
end deref
noderef() --> item 1 of {"A", "B", "C"}
deref() --> "A"
Both of your handlers pass the reference, which is true to the Perl semantics, but might be a bit surprising to some AppleScript users. You might consider abandoning the Perl semantics and passing the dereferenced value, or embracing the Perl semantics and documenting the reference (which unfortunately is not quite as transparent as the way Perl does it).
Using map to Remove Items or Add Extra Items
In Perl map sub/block you can return zero, one, or more values. If you want to more fully embrace the Perl semantics, you could do the same thing. The gotcha here is that to map to a list of exactly one item you have to wrap the value in another list. This does not come up in Perl because returning a list is different from returning an “arrayref” (return @somelist vs. return [@somelist]).
Here is a more Perl-y version with some of the other changes I have mentioned:
on map over theList given script:theScript
set resultList to {}
repeat with theItem in theList
set newValue to theScript's lambda(theItem)
if class of newValue is list then
if length of newValue is 1 then
-- can directly append because we only have one item to append
set end of resultList to first item of newValue
else if length of newValue is greater than 1 then
-- must concatenate because we are appending more than one item
set resultList to resultList & newValue
end if
else
-- can directly append because we only have one item to append
set end of resultList to newValue
end if
end repeat
return resultList
end map
on grep over theList given script:theScript
set resultList to {}
repeat with theItem in theList
if theScript's lambda(theItem) then
set end of resultList to contents of theItem
end if
end repeat
return resultList
end grep
-- end library
script mapScript
property HowMany : 0
on lambda(someone)
set HowMany to HowMany + 1
set newValue to someone & " Gardner " & HowMany
if HowMany is equal to 1 then
-- map to a list of one item
set newValue to {{newValue}}
else if HowMany is equal to 2 then
-- map to nothing
set newValue to {}
else if HowMany is equal to 3 then
-- modify original list and
-- map to multiple values
set contents of someone to "frobbed!"
set newValue to {"-->", newValue, "<--"}
end if
newValue
end lambda
end script
set aList to {"Mark", "Erin", "David"}
map over aList given script:mapScript -->{{"Mark Gardner 1"}, "-->", "David Gardner 3", "<--"}
aList --> {"Mark", "Erin", "frobbed!"}
script grepScript
on lambda(someone)
set val to contents of someone
considering case
if val is equal to "David" then
return true
end if
if val is equal to "Mark" then
-- modify original list
set contents of someone to "also frobbed!"
end if
return false
end considering
end lambda
end script
set aList to {"Mark", "Erin", "David"}
grep over aList given script:grepScript --> {"David"}
aList --> {"also frobbed!", "Erin", "David"}
Passing a Handler Without a Script Object
It is possible to pass a handler directly, but it might not do what you want:
property foo : "global"
to doSomething(x)
"something (" & x & "; " & foo & ")"
end doSomething
to doSomethingBy by x
"something else (" & x & "; " & foo & ")"
end doSomethingBy
to useIt(aHandler)
script wrapper
property foo : "useIt"
property theHandler : missing value
end script
set wrapper's theHandler to aHandler
wrapper's theHandler("x")
end useIt
to useItBy(aHandler)
script wrapper
property foo : "useItBy"
property theHandler : missing value
end script
set wrapper's theHandler to aHandler
wrapper's (theHandler by "x")
end useItBy
global aGlobalHandler
to gUseIt(aHandler)
set aGlobalHandler to aHandler
aGlobalHandler("g")
end gUseIt
to gUseItBy(aHandler)
set aGlobalHandler to aHandler
aGlobalHandler by "g"
end gUseItBy
script bla
property foo : "bla"
to blah(x)
"blah (" & x & "; " & foo & ")"
end blah
to blahBy by x
"blah by (" & x & "; " & foo & ")"
end blahBy
end script
useIt(doSomething) --> "something (x; useIt)"
gUseIt(doSomething) --> "something (g; global)"
useItBy(doSomethingBy) --> "something else (x; useItBy)"
gUseItBy(doSomethingBy) --> "something else (g; global)"
bla's blah("b") --> "blah (b; bla)"
blahBy of bla by "b" --> "blah by (b; bla)"
useIt(bla's blah) --> "blah (x; useIt)"
gUseIt(bla's blah) --> "blah (g; global)"
useItBy(bla's blahBy) --> "blah by (x; useItBy)"
gUseItBy(bla's blahBy) --> "blah by (g; global)"
I have found passing script objects to be much cleaner and less error prone (e.g. I get a very misleading error message if the wrapper scripts do not include their own foo property: “Can’t make «handler doSomething» into type string.” with foo in doSomething highlighted).
Model: iBook G4 933
AppleScript: 1.10.7
Browser: Safari 4.0.4 (4531.21.10, r51280)
Operating System: Mac OS X (10.4)