Sorting anything with Execute JavaScript actions

Updated code – a JavaScript version (with examples) of the sortOn(f, xs) function posted for Applescript at:

(() => {
    'use strict';

    // Sort a list by comparing the results of a key function applied to each
    // element. sortOn f is equivalent to sortBy (comparing f)

    // sortOn :: Ord b => (a -> b) -> [a] -> [a]
    const sortOn = (f, xs) => {
        // Functions and matching bools derived from argument f
        // which may be a single key function, or a list of key functions
        // each of which may or may not be followed by a direction bool
        const [fs, bs] = unzip(
                flatten([f])
                .reduceRight((a, x) =>
                    typeof x === 'boolean' ? {
                        asc: x,
                        fbs: a.fbs
                    } : {
                        asc: true,
                        fbs: [
                            [x, a.asc]
                        ].concat(a.fbs)
                    }, {
                        asc: true,
                        fbs: []
                    })
                .fbs
            ),
            iLast = fs.length;
        // decorate-sort-undecorate
        return sortBy(mappendComparing(
                // functions that access pre-calculated values by position
                // in the decorated ('Schwartzian') version of xs
                zip(
                    fs.map((_, i) => x => x[i]),
                    bs
                )
            ), xs.map( // xs decorated with precalculated key function values
                x => fs.reduceRight(
                    (a, g) => [g(x)].concat(a), [
                        x
                    ])))
            .map(x => x[iLast]); // undecorated version of data, post sort.
    };

    // -----------------------------------------------------------------------

    // compare :: a -> a -> Ordering
    const compare = (a, b) => a < b ? -1 : (a > b ? 1 : 0);

    // flatten :: Tree a -> [a]
    const flatten = t =>
        Array.isArray(t) ? (
            [].concat.apply([], t.map(flatten))
        ) : t;

    // mappendComparing :: [((a -> b), Bool)] -> (a -> a -> Ordering)
    const mappendComparing = fboolPairs =>
        (x, y) => fboolPairs.reduce(
            (ord, [f, b]) => ord !== 0 ? (
                ord
            ) : (
                b ? compare(f(x), f(y)) : compare(f(y), f(x))
            ), 0
        );

    // show :: Int -> a -> Indented String
    // show :: a -> String
    const show = (...x) =>
        JSON.stringify.apply(
            null, x.length > 1 ? [x[1], null, x[0]] : x
        );

    // sortBy :: (a -> a -> Ordering) -> [a] -> [a]
    const sortBy = (f, xs) =>
        xs.slice()
        .sort(f);

    // unzip :: [(a,b)] -> ([a],[b])
    const unzip = xys =>
        xys.reduceRight(([xs, ys], [x, y]) => [
            [x].concat(xs), [y].concat(ys)
        ], [
            [],
            []
        ]);

    // zip :: [a] -> [b] -> [(a, b)]
    const zip = (xs, ys) =>
        xs.slice(0, Math.min(xs.length, ys.length))
        .map((x, i) => [x, ys[i]]);

    // TEST -------------------------------------------------------------------

    // Data to sort

    // greekLetterNames :: () -> [String]
    const greekLetterNames = () => [
        "alpha", "beta", "gamma", "delta", "epsilon", "zeta",
        "eta", "theta", "iota", "kappa", "lambda", "mu"
    ];

    // cities :: () -> [Dict]
    const cities = () => [{
            city: "Shanghai",
            pop: 24.3,
            country: "China",
            capital: false
        },
        {
            city: "Beijing",
            pop: 21.5,
            country: "China",
            capital: true
        },
        {
            city: "Delhi",
            pop: 11.0,
            country: "India",
            capital: true
        },
        {
            city: "Lagos",
            pop: 16.0,
            country: "Nigeria",
            capital: true
        },
        {
            city: "Karachi",
            pop: 14.9,
            country: "Pakistan",
            capital: false
        },
        {
            city: "Dhaka",
            pop: 14.5,
            country: "Bangladesh",
            capital: true
        },
        {
            city: "Guangzhou",
            pop: 14.0,
            country: "China",
            capital: false
        },
        {
            city: "Istanbul",
            pop: 14.0,
            country: "Turkey",
            capital: false
        },
        {
            city: "Tokyo",
            pop: 13.5,
            country: "Japan",
            capital: true
        }
    ];

    // Key Functions for sort descriptors ------------------------------------

    // country :: Dict -> String
    const country = x => x.country;

    // isCapital :: Dict -> Bool
    const isCapital = x => x.capital;

    // population :: Dict -> Num
    const population = x => x.pop;

    // reverseString :: String -> String
    const reverseString = s =>
        s.split('')
        .reverse()
        .join('');

    // stringLength :: String -> Int
    const stringLength = s =>
        s.length;

    return [
        // Sorting from suffix toward prefix, ASCENDING

        sortOn(reverseString, greekLetterNames()),

        // From -da up to -mu
        // -> ["lambda","alpha","gamma","kappa","eta","beta","theta",
        //  "zeta","delta","iota","epsilon","mu"]

        // Sorting from suffix toward prefix, DESCENDING

        sortOn([reverseString, false], greekLetterNames()),

        // From -mu down to -da
        // -> ["mu","epsilon","iota","delta","zeta","theta","beta","eta",
        //     "kappa","gamma","alpha","lambda"]


        // Primary sort : Country name Ascending ('Bangladesh' first)
        // Secondary sort : isCapital Boolean Descending (True then False)
        //     Beijing, as capital, comes before Shanghai and Guangzhou.
        // Tertiary sort : Population Descending - Shanghai before Guangzhou

        sortOn([country, isCapital, false, population, false], cities()),

        // Key functions can optionally be bracketed with any direction bools
        // that follow them, for legibility.

        sortOn([country, [isCapital, false], [population, false]], cities())
    ];
})();