JavaScript / JXA Q&A

I do like them -- sometimes :wink:

For those that would like more info, see:
Conditional (ternary) Operator - JavaScript | MDN

1 Like

When I get content off the clipboard like this:

var app = Application.currentApplication();
app.includeStandardAdditions = true;
var s = app.theClipboard();

Let’s say I’m running the above code, and I want to see what “type” it returned - undefined, an empty string, some other type… How can I tell? I’m talking about eyeballing the result, not actual JS code to test it. If I just “return” it, that doesn’t really tell me, unless it happens to be a string. In C# I would look at it in the debugger, but when I use the Safari debugger, it doesn’t really tell me. Or does it and I just don’t know what I’m seeing?

However, I’d also like to know what shorthand code i can use to say “is this a non-empty string?”

Thanks.

ObjC.import('AppKit');

ObjC.deepUnwrap(
    $.NSPasteboard.generalPasteboard.pasteboardItems.js[0].types
)

Yeah, I know that one. My question was more generic. I should have asked “how can I tell what type any variable is?” I just don’t know whether to check for null, or undefined, or something else…

mixed:

typeof (variable name) returns a string

But a lot of things are Objects, so then you may want to do specific things like:

If (x instanceof Array) {
}

if (x !== undefined) {
}

1 Like

The below posts were moved from:

Hey Rob - Just came across this, and it looks really interesting. A few questions:

  1. You have a “Caution” comment in the beginning. Since you’re only reading from the plist file, is there really a possibility of screwing anything up? If you’d rather not state the answer, you could do it in a PM - I understand liability concerns. I just want to know what the potential issues are, if I do the same thing.

  2. I’m trying to wrap my head around “.reduce”, and I feel like I’m close. Is it possible you’re using .reduce as more of a “foreach”? It kind of looks that way, since you do some ".push"s in it. If not, can you offer more insight?

  3. Is there a reason you have a separate “nameSort” function, instead of just including it as an inline function when you call “.sort”? I ask because for everything else, you don’t use separate functions - you inline them (what’s the right terms for the distinction I’m making?).

Thanks.

  1. No danger for you, just squeamish about recommending anyone to experiment at all with (or even be conscious of the whereabouts of) a file which contains so much work and resource :slight_smile:
  2. Reduce is JavaScript's version of fold or foldl / foldLeft, which is just a very general relationship between a list and any single value which can be derived from it. See more below.
  3. The same function is used in two places there, one inside the reduce and one outside, so to minimize maintenance I just bound a name to it in one place.

The short version of fold (reduce) was recently tweeted as:

A fuller version, worth skimming, I think, because fold/reduce is so universal and powerful, is at:

And here are some simple illustrative folds (most of them left folds, one of them a right fold (reduceRight in JS). A surprising number of things, even map and filter, can be written as a fold. Good instrument to have in the toolbag.

(function () {
    'use strict';

    // sum :: [Number] -> Number
    function sum(lstNumbers) {
        return lstNumbers
            .reduce(
                function (accumulator, x) {
                    return accumulator + x;
                },
                0 // initial value of accumulator
            )
    }

    // length :: [a] -> Integer
    function length(ListOfAnything) {
        return ListOfAnything
            .reduce(
                function (accumulator, x) {
                    return accumulator + 1;
                },
                0 // initial value of accumulator
            )
    }

    // filtered :: (a -> Bool) -> [a] -> [a]
    function filtered(f, lst) {
        return lst
            .reduce(
                function (accumulator, x) {
                    return (
                        f(x) && accumulator.push(x),
                        accumulator
                    )
                }, [] // initial value of accumulator is empty list
            )
    }

    // reversed :: [a] -> [a]
    function reversed(lst) {
        return lst
            .reduceRight(
                function (accumulator, x) {
                    return (
                        accumulator.push(x),
                        accumulator
                    );
                }, [] // initial value of accumulator is empty list
            )
    }

    function isEven(n) {
        return n % 2 === 0
    }

    return {
        sum: sum([1, 2, 3, 4, 5, 6, 7, 8, 9]),

        length: length([1, 2, 3, 4, 5, 6, 7, 8, 9]),

        filtered: filtered(isEven, [1, 2, 3, 4, 5, 6, 7, 8, 9]),

        reversed: reversed([1, 2, 3, 4, 5, 6, 7, 8, 9])
    }

})();
1 Like

Heh, you have a higher regard for my level of intelligence than is warranted. That paper speaks in a language with which I'm not familiar. But thanks for thinking so highly of me! :slight_smile:

Ah, I get it (your examples). I was skipping over the fact you were passing an empty array, initially. I saw the comment but it didn't stick.

For you examples, there are alternatives, aren't there? Things like "filter" and the like, right? You were just demonstrating the method, right?

See, in C#, I'm used to extension methods like Aggregate, Where, First, Max, etc.

So I don't really even think much about how they're accomplished. For these methods, I just need to return whatever I want accumulated, or true/false, etc, and I don't even think about how it's done on the back-end. I mean, obviously I know what's happening, but I don't need to even consider it.

It's good to have something for me to compare with.

Sure, there are pre-cooked alternatives of those examples in most languages including JS, and fold/reduce itself can be implemented in various ways, recursively or iteratively.

The great thing about it is that its a well-tested and very general black box, which can protect you from having to mess with the details (declaring and incrementing iterators) and get things wrong (out of bounds errors etc), while tightening up scope, in a surprising range of different contexts.

1 Like

Don’t get me wrong - I think it’s great! For me, I need to be able to say “that thing is kind of like this thing”. So knowing what this is similar to, for instance I know it’s a hammer, and I know how to use a hammer, even if the handle on this one is different, I’m good.

But if someone tries to explain it to me, saying it’s a device that operates on the principal of inertia, mass and acceleration (or whatever), then I’m lost.

So thanks!

1 Like

Rob, many thanks for your tutorial on fold/reduce. Very interesting.

Since you seem to be in an educational mood, I wonder if you could help neophytes like me better understand some of the symbols I've seen you use in your scripts?

From the fold article:

I've often seen you use this notation, but I don't understand it.
For example:
// writePlist :: Object -> String -> IO ()

I did do some reasearch, and found this, but it does not include the double colon ::
Mathematical Symbols

So, if you have the time and interest, an explanation of (or a reference for) the above symbols used in the fold statement would be great!

Thanks.

The type signature

map :: (a -> b) -> [a] -> [b]

would be a brief way of announcing the intention that a function named map

had, as its first argument, another function with the type signature (a -> b)

(a function (a -> b) is taking an argument of any type, and returning a value which may be of a different type)

and as its second argument: an array of values of type a.

Finally it tells us that the output of this function is intended to be an array of values of type b.

(In other words, of any consistent type, which doesn't have to be the same as type a, but will be the same as the output of the (a -> b) function.

JavaScript is not a strongly typed language, but it can still be helpful to use type signatures to communicate clarity about the intended type behaviour of a function.

See for example, the entries in this JS library:

http://ramdajs.com/docs/#chain


If you work with Haskell, in which the compiler can actually make use of the optional type signature, there is a very useful search engine (Hoogle) which allows you to find functions by their type signature rather than by their name.

There is, in fact, a sense in which the type signature is more eloquent and fundamental than the name.

https://www.haskell.org/hoogle/

1 Like

Rob, merged per your request. Please edit as needed.

1 Like

Got another question. Consider this code:

function getKMMacroAndGroupByMacroUUID(macroUUID, plist) {
    plist.forEach(function(group) {
        var macros = group.macros;
        if (macros) {
            var macro = macros.find(function(m) {
                return m.uid === macroUUID;
            });
            if (macro) {
                return {
                    macro: macro,
                    group: group
                };
            }
        }
    });
    return undefined;
}

Obviously this doesn’t work. I’m looking through an array of Groups that may have an array of Macros. I want to find a Macro with a particular UUID (UID), and if I find it, return the Macro and its Group.

I wasn’t really thinking, and I tried to do it with a “return” in the anonymous function that the “forEach” calls, and of course that doesn’t work.

I can get something to work, of course, and I will, but I thought this was probably the perfect time to learn one of those fancy-sounding functions. :slight_smile:

This is what I came up with:

function getKMMacroAndGroupByMacroUUID(macroUUID, plist) {
    var _result = undefined;
    plist.find(function(group) {
        var macros = group.macros;
        if (!macros) {
            return false;
        }

        var macro = macros.find(function(m) {
            return m.uid === macroUUID;
        });
        if (macro) {
            _result = { macro: macro, group: group };
            return true;
        }
        return false;
    });
    return _result;
}

Using “find” allows me to exit the iteration loops immediately if I get a hit. Using a result variable in the outer scope allows the result to be accessible after the loops exit.

Is there a better way? This doesn’t smell too awful.

Looks good.

The only thing that jumps to the eye is case sensitivity in JS names, so for example, I think you may need:

  • group.Macros
  • m.UID

I’m very much in favour of built-in array methods like find.

If you ever wanted to do it more iteratively, the while(i--) idiom is typically the fastest with JS compilers, so one other way might be something like:

(function () {
    'use strict';


    function macroUUIDFound(lstGroups, strUID) {
        var i = lstGroups.length,
            dctGroup,
            lstMacros;

        while (i--) {
            dctGroup = lstGroups[i];
            lstMacros = dctGroup.Macros;

            if (lstMacros && lstMacros
                .find(function (m) {
                    return strUID === m.UID;
                })) {
                return true;
            }
        }
        return false;
    }


    var lstSeln = Application("Keyboard Maestro")
        .selectedmacros(),
        strUID = lstSeln.length ? lstSeln[0] : undefined;


    var lstMacroGroups = strUID ? ObjC.deepUnwrap(
            $.NSDictionary.dictionaryWithContentsOfFile(
                $(
                    '~/Library/Application Support/Keyboard Maestro/Keyboard Maestro Macros.plist'
                )
                .stringByStandardizingPath.js
            )
        )
        .MacroGroups : undefined;



    return strUID ? macroUUIDFound(lstMacroGroups, strUID) : "No macro selected"
})();

You would think that, wouldn't you? That's what I thought also. Except in this case, they're lower case. I suspect this is a mistake on Peter's part, when he returns the result of "getmacros". But let's not tell him, OK? :slight_smile:

But I'm assuming, since you said you're a fan of built-in methods, that you normally wouldn't do that unless speed was an issue, right? 'Cause to me, that feels like going back to the dark ages. :open_mouth: :stuck_out_tongue:

Except in this case, they're lower case.

That's interesting ... I wonder if we are working with different builds ?

(On this system it fails with lower case, and shows Upper in the debugger ... )

that feels like going back to the dark ages.

Absolutely agree. Performance seldom seems a genuine or even perceptible issue in Mac scripting – scripter time is certainly real.

Here's what I'm using (I'm editing this slightly manually, so if it doesn't compile, fix it. :slight_smile:)

var _kme = Application("Keyboard Maestro Engine");
var macros = kme.getmacros({
    asstring: true
});
console.log(macros);