Is There Way For an AppleScript or JXA script To Obtain the Name or Id of the Macro That Is Executing The Script?

No, it‘s a great idea, and I intend to do likewise from now on. I was just suggesting that other people trying to make sense of the post might also get tripped up by the extra parameters.

I hand‘t even realized that extra parameters are ignored in AppleScript, rather than causing an error (except that parameters cannot be included when invoking a 0-parameter handler). In fact, was this always the case? If not, when did it start being allowed? I can‘t find any evidence in the About Handlers section of the AppleScript Language Guide that parameters many be omitted when invoking a handler definition with positional parameters.

You can only get the parameters to be optional in that special pattern where you are ‘lifting’ a second-class handler into a first class script (with a property which is a reference to the handler).

Generally, I write things like map, filter, and catamorphisms (reduce/fold) in AS in this form, using a higher-order mReturn function:

-- map :: (a -> b) -> [a] -> [b]
on map(f, xs)
    tell mReturn(f)
        set lng to length of xs
        set lst to {}
        repeat with i from 1 to lng
            set end of lst to lambda(item i of xs, i, xs)
        end repeat
        return lst
    end tell
end map

-- filter :: (a -> Bool) -> [a] -> [a]
on filter(f, xs)
    tell mReturn(f)
        set lst to {}
        set lng to length of xs
        repeat with i from 1 to lng
            set v to item i of xs
            if lambda(v, i, xs) then set end of lst to v
        end repeat
        return lst
    end tell
end filter

-- foldl :: (a -> b -> a) -> a -> [b] -> a
on foldl(f, startValue, xs)
    tell mReturn(f)
        set v to startValue
        set lng to length of xs
        repeat with i from 1 to lng
            set v to lambda(v, item i of xs, i, xs)
        end repeat
        return v
    end tell
end foldl

-- foldr :: (a -> b -> a) -> a -> [b] -> a
on foldr(f, startValue, xs)
    tell mReturn(f)
        set v to startValue
        set lng to length of xs
        repeat with i from lng to 1 by -1
            set v to lambda(v, item i of xs, i, xs)
        end repeat
        return v
    end tell
end foldr

-- scanl :: (b -> a -> b) -> b -> [a] -> [b]
on scanl(f, startValue, xs)
    tell mReturn(f)
        set v to startValue
        set lng to length of xs
        set lst to {startValue}
        repeat with i from 1 to lng
            set v to lambda(v, item i of xs, i, xs)
            set end of lst to v
        end repeat
        return lst
    end tell
end scanl

-- scanr :: (b -> a -> b) -> b -> [a] -> [b]
on scanr(f, startValue, xs)
    tell mReturn(f)
        set v to startValue
        set lng to length of xs
        set lst to {startValue}
        repeat with i from lng to 1 by -1
            set v to lambda(v, item i of xs, i, xs)
            set end of lst to v
        end repeat
        return reverse of lst
    end tell
end scanr

-- Lift 2nd class handler function into 1st class script wrapper 
-- mReturn :: Handler -> Script
on mReturn(f)
    if class of f is script then
        f
    else
        script
            property lambda : f
        end script
    end if
end mReturn

Wow. How did you ever figure that out?! (Or where did you find it?)

He’s smart like that. Get used to it. :slight_smile:

Also, never ask him leading questions like that. :stuck_out_tongue:

1 Like

pff … I wanted to use a map reduce compositional style in AppleScript, found that handlers were second class objects, and tinkered around with ways of lifting them into a first class ‘script monad’.

(It went through a couple of clumsier stages before settling down :slight_smile: )

mReturn is named as an echo of the value-wrapping (value-lifting, if you prefer)

return :: a -> m a

in:

https://wiki.haskell.org/Monad#Monad_class

PS another helpful thing in structuring AS is that you can build arbitrary depths of closure by nesting Scripts inside Handlers.

At the simplest level, concatMap (useful for implementing list comprehensions) can be written like this (which, with AS and JS equivalents of most of the Haskell Prelude functions, is in Data.List AppleScript in that Quiver file, incidentally)

-- concatMap :: (a -> [b]) -> [a] -> [b]
on concatMap(f, xs)
    script append
        on lambda(a, b)
            a & b
        end lambda
    end script
    
    foldl(append, {}, map(f, xs))
end concatMap


-- foldl :: (a -> b -> a) -> a -> [b] -> a
on foldl(f, startValue, xs)
    tell mReturn(f)
        set v to startValue
        set lng to length of xs
        repeat with i from 1 to lng
            set v to lambda(v, item i of xs, i, xs)
        end repeat
        return v
    end tell
end foldl

-- map :: (a -> b) -> [a] -> [b]
on map(f, xs)
    tell mReturn(f)
        set lng to length of xs
        set lst to {}
        repeat with i from 1 to lng
            set end of lst to lambda(item i of xs, i, xs)
        end repeat
        return lst
    end tell
end map

-- Lift 2nd class handler function into 1st class script wrapper 
-- mReturn :: Handler -> Script
on mReturn(f)
    if class of f is script then
        f
    else
        script
            property lambda : f
        end script
    end if
end mReturn

So I wonder: bug/feature?

So I wonder: bug/feature?

architecture, I think

I realize this is an old thread, but I just came across it and was highly impressed (and intrigued) by your use of things like closures, lambdas, and mapping in AppleScript. (Thank you for these little nuggets of insight).

Do you have any go-to ‘tricks’ or scripts for working more efficiently with AppleScript records? I use AppleScript for quite a bit of things, but have gotten a bit spoiled by the robustness of ‘similar’ data structures (hashes, dicts, structs, etc.) available in other languages. Anyway, I always enjoy seeing how others implement ‘non-native’ features in AppleScript.

To read the set of keys in a record, and request a value for a key which a record may not have, I rely on this kind of thing in AppleScript:

Note that all of these functions need the two import lines at the start, 1. to use the Foundation framework, and 2. to bring the scripting additions back out of the shadowing caused by that framework.

use framework "Foundation"
use scripting additions

-- elems :: Dict -> [a]
on elems(rec)
    set ca to current application
    (ca's NSDictionary's dictionaryWithDictionary:rec)'s allValues() as list
end elems

-- keys :: Dict -> [String]
on keys(rec)
    (current application's NSDictionary's dictionaryWithDictionary:rec)'s allKeys() as list
end keys


-- lookup :: Eq a => a -> Container -> Maybe b
-- lookup :: Eq a => a -> [(a, b)] -> Maybe b
-- lookup :: Key -> Dict -> Maybe a
on lookup(k, m)
    set c to class of m
    if c is list then
        repeat with kv in m
            set v to contents of kv
            if length of v > 1 and item 1 of v = k then
                return {nothing:false, just:item 2 of kv}
            end if
        end repeat
    else if c = record then
        set ca to current application
        set v to (ca's NSDictionary's dictionaryWithDictionary:m)'s objectForKey:k
        if v ≠ missing value then
            return {nothing:false, just:item 1 of ((ca's NSArray's arrayWithObject:v) as list)}
        end if
    end if
    {nothing:true} -- m of unknown type, or k not found
end lookup

mapInsert is probably less necessary – AppleScript can at least produce the same side-effect, though this kind of function is a bit more composable:

use framework "Foundation"
use scripting additions

-- mapInsert :: Dict -> k -> v -> Dict
on mapInsert(rec, k, v)
    set ca to current application
    set nsDct to (ca's NSMutableDictionary's dictionaryWithDictionary:rec)
    nsDct's setValue:v forKey:(k as string)
    item 1 of ((ca's NSArray's arrayWithObject:nsDct) as list)
end mapInsert

For example, converting an 'association list' of key-value pairs like:

{{"alpha", 1}, {"beta", 2}, {"gamma", 3}, {"delta", 4}}

to an AppleScript record like:

{gamma:3, delta:4, alpha:1, beta:2}

and also converting from a record to an association list:

AppleScript source:

use framework "Foundation"
use scripting additions

--  pairListAsRecord :: [(Key, a)] -> Dict
on pairListAsRecord(kvs)
    script addedToRecord
        on |λ|(accumulator, kv)
            set {k, v} to kv
            mapInsert(accumulator, k, v)
        end |λ|
    end script
    foldl(addedToRecord, {name:""}, kvs)
end pairListAsRecord

--  recordAsPairList :: Dict -> [(Key, a)]
on recordAsPairList(rec)
    zip(my keys(rec), my elems(rec))
end recordAsPairList

-- TEST ------------------------------------------------------------------
on run
    -- kvs :: [(Key, Value)]
    set kvs to {{"alpha", 1}, {"beta", 2}, {"gamma", 3}, {"delta", 4}}
    
    
    -- ASSOCIATION LIST CONVERTED TO APPLESCRIPT RECORD
    set rec to pairListAsRecord(kvs) --> {gamma:3, delta:4, alpha:1, beta:2}
    log rec
    
    --     APPLESCRIPT RECORD CONVERTED TO ASSOCIATION LIST
    set associationList to recordAsPairList(rec)
end run

-- elems :: Dict -> [a]
on elems(rec)
    set ca to current application
    (ca's NSDictionary's dictionaryWithDictionary:rec)'s allValues() as list
end elems

-- foldl :: (a -> b -> a) -> a -> [b] -> a
on foldl(f, startValue, xs)
    tell mReturn(f)
        set v to startValue
        set lng to length of xs
        repeat with i from 1 to lng
            set v to |λ|(v, item i of xs, i, xs)
        end repeat
        return v
    end tell
end foldl

-- keys :: Dict -> [String]
on keys(rec)
    (current application's NSDictionary's dictionaryWithDictionary:rec)'s allKeys() as list
end keys

-- mapInsert :: Dict -> k -> v -> Dict
on mapInsert(rec, k, v)
    set ca to current application
    set nsDct to (ca's NSMutableDictionary's dictionaryWithDictionary:rec)
    nsDct's setValue:v forKey:(k as string)
    item 1 of ((ca's NSArray's arrayWithObject:nsDct) as list)
end mapInsert

-- mapKeys :: (Key -> Key) -> IntMap a -> IntMap a
on mapKeys(f, dct)
    script
        property g : mReturn(f)
        on |λ|(kv)
            set {k, v} to kv
            {g's |λ|(k), v}
        end |λ|
    end script
    map(result, zip(keys(dct), elems(dct)))
end mapKeys

-- min :: Ord a => a -> a -> a
on min(x, y)
    if y < x then
        y
    else
        x
    end if
end min

-- Lift 2nd class handler function into 1st class script wrapper
-- mReturn :: First-class m => (a -> b) -> m (a -> b)
on mReturn(f)
    if class of f is script then
        f
    else
        script
            property |λ| : f
        end script
    end if
end mReturn

-- zip :: [a] -> [b] -> [(a, b)]
on zip(xs, ys)
    set lng to min(length of xs, length of ys)
    set lst to {}
    repeat with i from 1 to lng
        set end of lst to {item i of xs, item i of ys}
    end repeat
    return lst
end zip

I'm just wondering...;;
Since we can access Local and Instance variable in JXA
https://forum.keyboardmaestro.com/uploads/default/original/2X/5/535ead5e89e0af65ad08addf4309b564e43a55ca.kmmacros
is there any potential to get access to %ExecutingMacro% and %ExecutingThisMacro% in JXA?
Or, this is not related at all?

Not currently, no.

You can store them in variables, which will then be accessible from the environment variables within the script.

Would %TriggerValue% be the same problem accessing from JXA?
Thanks!

The only Application('Keyboard Maestro Engine') methods which have optional instance arguments are .getvariable and .setvariable.

As the .processTokens method doesn't have an instance argument, we probably can't expect to obtain any instance-specific return values from token-processing.