omniJS - transitioning from AppleScript and tutorial code conventions

I imagine that some (many ?) new users of omniJS might at first be more accustomed to reading and writing AppleScript than JavaScript.

JavaScript allows for a number of styles, and glancing through the early drafts of the excellent https://omni-automation.com/ pages, it occurred to me that it might be worth thinking about what JS style conventions might turn out to be:

  1. Clearer for reading JS
  2. Less error-prone for writing JS

For example:

in lieu of

actionNames = new Array()

we could just write:

actionNames = []

which is not unfamiliar in spirit to an AppleScript statement like set actionNames to {}

I notice, FWIW, that Google’s JS guidelines also prefer these briefer and simpler ‘object literals’ like [] and {}.

https://google.github.io/styleguide/javascriptguide.xml?showone=Array_and_Object_literals#Array_and_Object_literals

Perhaps another example might be the three / four component:

  1. New array
  2. forEach
  3. Array.push
  4. Return

vs the one-stage Array.map ?

One of the delights of escaping from AppleScript to JavaScript is no longer being obliged to endlessly fiddle with setting up loops and tripping over their border conditions, so I am very pleased to see the first https://omni-automation.com/ examples already preferring Array.forEach to the messier and more complex for(initialize; condition; step) etc.

Having made that transition, though, I wonder if it might be cleaner or clearer to just go for Array.map ?

We could, for example, rewrite this 3 stage (Create, repeatedly push into, Return)

    actionNames = new Array()
    aPlugin.actions.forEach(function(action){
        actionNames.push(action.name)
    })
    return actionNames

as the single stage (new array from actions mapped to their names)

    return aPlugin.actions.map(function(action) {
        return action.name
    })

Relieving the user of all that array creation and repeated pushing - perhaps less error-prone to write as well as quicker to read ?

A pity, in a way, that pre-Sierra systems still need ES5 JS

These things are even easier to read and write in ES6 (Sierra+) JS

    [1, 2, 3, 4, 5].map(x => x * 2)
    
    // --> [2, 4, 6, 8, 10]

and

    return aPlugin.actions.map(action => action.name);

FWIW for that reason I personally write everything in ES6, and then, if I want to share/distribute, paste it into the Babel JS REPL at https://babeljs.io/repl/ to get an ES5 version for pre-Sierra compatibility

PS one could possibly argue that a map function is likely to be a bit of a novelty to any AppleScript-only users (are they numerous ? not sure … ), but on the other hand a map is perfectly doable and explainable in AppleScript terms - it’s just that AppleScript makes you jump through hoops for it, while JS gives it to you for free:

[details=AppleScript equivalent of [1, 2, 3, 4, 5].map(x => x * 2)]

on double(x)
    x * 2
end double

on run
    map(double, {1, 2, 3, 4, 5})
    
    --> {2, 4, 6, 8, 10}
end run


-- GENERIC FUNCTIONS ---------------------------------------------------------

-- 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 |λ|(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 |λ| : f
        end script
    end if
end mReturn

[/details]

As always, very thoughtful. Thank you.

My goal in writing documentation, especially containing scripts, it to be as clear and uncomplicated as possible. This approach implies that I not assume a level of competency and go with things that are obvious over efficient. I write for the newbie, figuring that those that are already proficient will know to re-write the examples the way they prefer. Typically, my examples are longer, but are hopefully understandable.

For example, I prefer to use:

myArray = new Array()

instead of:

myArray = []

because it actually says what it is doing: creating a new array

Likewise, I prefer to use the following because it is clear to a newbie what is going on: property values are being retrieved and added to a list:

   actionNames = new Array()
    aPlugin.actions.forEach(function(action){
        actionNames.push(action.name)
    })
    return actionNames

while use of map is more efficient, it requires a depth of knowledge that may not exist in the reader:

return aPlugin.actions.map(function(action) {
    return action.name
})

I also prefer to use descriptive variables, which typically make the code longer but hopefully more understandable!

BTW, it turns out that there can be issues on iOS when using forEach vs for loops in call backs. iOS is in constant “purge-mode” and sometimes the for loop is the way to go. These particular bugs will probably be worked out over time.

Thank you for the great feedback!

Sal

Understood.

( I guess even there some things could look novel. The notions of .push() and .pop(), for example, are not, I think, introduced / used by Applescript )

Perhaps a side-by-side page somewhere, mapping basic JS idioms to their Applescript equivalents ?

Great idea! I’ll add that to the punch list. Comparison of how basic things are done in each language. I need another life! LOL!

Not a side-by-side comparison, but rosettacode.org could be useful for AppleScript users trying to familiarize themselves with JavaScript, and vice versa.

For example:

Arrays/Lists–

http://rosettacode.org/wiki/Arrays#AppleScript
http://rosettacode.org/wiki/Arrays#JavaScript

Loops–

http://rosettacode.org/wiki/Loops/Foreach#AppleScript
http://rosettacode.org/wiki/Loops/Foreach#JavaScript

Perhaps the hard part for many will be understanding the nitty-gritty on how to have a script control the app, for which some dual examples will be invaluable.

SG

I agree – I also think that Rosetta Code can be a very useful reference.

About a 100+ of the Rosetta Code AppleScript and JavaScript examples are written in parallel to each other, using the approach of composing short scripts/programs from a set of c. 150 generic and reusable building-block functions which have the same name, (and same pattern of inputs and outputs) in both languages.

If you need a list of the same item repeated N times, for example, you could write essentially the same replicate function in AS and JS as:

--  replicate(n, x) is a list of length n with x the value of every element. 

[details=AppleScript version]

-- Egyptian multiplication - progressively doubling a list, appending
-- stages of doubling to an accumulator where needed for binary 
-- assembly of a target length

-- replicate :: Int -> a -> [a]
on replicate(n, x)
	set out to {}
	if n < 1 then return out
	set dbl to {x}
	
	repeat while (n > 1)
		if (n mod 2) > 0 then set out to out & dbl
		set n to (n div 2)
		set dbl to (dbl & dbl)
	end repeat
	return out & dbl
end replicate
```[/details]

[details=Javascript version]
In Yosemite onwards (ES5) JavaScript:

```Javascript
// replicate :: Int -> a -> [a]
function replicate(n, x) {
    var v = [x],
        o = [];
    if (n < 1) return o;
    while (n > 1) {
        if (n & 1) o = o.concat(v);
        n >>= 1;
        v = v.concat(v);
    }
    return o.concat(v);
};

(In Sierra onwards, you can, if you want, write this in the simpler ES6 Javascript idiom) :

// replicate :: Int -> a -> [a]
const replicate = (n, x) =>
    Array.from({
        length: n
    }, ()=> x);

[/details]

The idea being that if you do want (for example) a replicated list of something, you just reach for an existing generic building-block function (which works the same way in both languages) rather than having to think its contents through or reinvent it.


PS Rosetta Code does, however, seem to time out quite often – especially at weekends …

Another quick thought on this theme – the value of protecting any early JS users from getting confused and tripped up.

One classic stumbling block is the innocent-looking “==” which performs easily-confusing type conversions without letting you know.

“===” is faster and simpler – no puzzling type conversions. Perhaps helpful (and more transferable) to use it from the beginning ?

This is the kind of thing that “==” trips us up with:

// JavaScript

var n = 5;
n == 5; //true
n == "5"; //  true    ... but is that what we expected or wanted ? 

// vs.  ( An integer is not the same thing as a string )

n === 5; // true
n === "5"; // false

Particularly confusing for users of Applescript, which doesn’t use the slippery logic of “==”.

-- Applescript

5 = 5

    --> true

5 = "5"

    --> false