ObjC bindFunction equivalent for Math functions in AppleScript?

‘Simply’ might be more accurately replaced by ‘sometimes’ :slight_smile:

For 50% of integer or real inputs it fails, of course, and halts execution. In addition, it’s not a composable expression.

It clearly does, at the very least, need some kind of wrapper:

  1. for composition of larger expressions, and for use as an argument of other functions, for mapping or folding over lists, etc etc,
  2. because its only a partial operator, which, when the input is negative (as half of numeric ranges are), simply throws up its hands and interrupts execution (rather than returning a more useful NaN or Nothing type of value). Even the error message is of questionable quality …
-1 ^ 0.5

50

A lot to be said for a coherent and consistently composable set of well-tested functions. Yes, we could write our own wrappers and error handlers for the AS n ^ 0.5, but there are many contexts in which that might just be a waste of time.

On that, we agree. I see the need for these functions in AppleScript as being primarily in terms of calculations needed to drive applications – for just about any other chore it’s a very poor choice of tool. In that context, returning NaN is probably just kicking an error down the road.

2 Likes

And, of course, best practice down the road is always safer (and gets you there sooner) than a high-speed breakdown on the highway :slight_smile:

In case it’s not clear, I also agree entirely that using well-tested code from outside is generally preferable. I wouldn’t have added trig commands to my BridgePlus framework if I thought otherwise. My concern, really, is with accessing it in a somewhat opaque and non-debuggable way. That’s not a problem for you, but it can be for anyone who wishes to use the code in their own scripts.

1 Like

I hear what you’re saying, and I make no claims for my own code, which I’m always happy to share, but which is optimized for my own work and preferences.

On the other hand, opacity is, of course, in the eye of the beholder, and likewise ease of debugging.

For accessibility to the widest range of office-workers, students and professionals with no particular interest or formal training in computation, the most successful model (learnability and adoption, profitability and contribution to productivity) is clearly that of spreadsheet scripting.

Specifically, that exceptionally learnable and successful model is to build things with fixed name bindings and nested functions, rather than with sequenced ‘actions’ and unconstrained mutations of state and name-space.

In Excel for example =sqrt(-5) evaluates to #NUM!, which can be an argument to the function ifError(value, value_if_error), which itself returns a value.

One can certainly, with time and application, become a specialist in stepping through complex state mutations and error dialogs, and the burdens of doing that, and acquiring those specialist skills, are eased by excellent tools like Script Debugger.

But hundreds of thousands of office workers find a well-defined structure of nested functions and immutable values much less opaque, and very much easier to debug.

Fortunately, it’s a model which can be adopted in AppleScript and JavaScript too.

I was meaning in terms of technical limitations of AppleScript. There are some structures that prevent AppleScript from returning accurate error range information, and some that preclude using debugging in Script Debugger. Unfortunately they hold regardless of the beholder.

Interesting trade-off – perhaps the very same structures that reduce the need for debugging.

Speaking of which, is your Applescript environment struggling to evaluate maps filters and folds like these ?

If so, I wonder if it is shadowed by some 3rd party addition to your environment ? An OSAX ?

Simple mapping, filtering and reducing – as in Excel summary cells and derived rows or columns:

on double(x)
    x * 2
end double

on sum(a, b)
    a + b
end sum

-- TEST ----------------------------------------------------------------------
on run
    
    set xs to enumFromTo(1, 10)
    
    {map(double, xs), filter(even, xs), foldl(sum, 0, xs)}
    
end run


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

-- enumFromTo :: Enum a => a -> a -> [a]
on enumFromTo(m, n)
    if class of m is integer then
        enumFromToInt(m, n)
    else
        enumFromToChar(m, n)
    end if
end enumFromTo

-- enumFromToInt :: Int -> Int -> [Int]
on enumFromToInt(m, n)
    if m ≤ n then
        set lst to {}
        repeat with i from m to n
            set end of lst to i
        end repeat
        return lst
    else
        return {}
    end if
end enumFromToInt

-- even :: Int -> Bool
on even(x)
    x mod 2 = 0
end even

-- 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 |λ|(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 |λ|(v, item i of xs, i, xs)
        end repeat
        return v
    end tell
end foldl

-- intercalate :: [a] -> [[a]] -> [a]
-- intercalate :: String -> [String] -> String
on intercalate(sep, xs)
    concat(intersperse(sep, xs))
end intercalate

-- intersperse(0, [1,2,3]) -> [1, 0, 2, 0, 3]
-- intersperse :: Char -> String -> String
-- intersperse :: a -> [a] -> [a]
on intersperse(sep, xs)
    set lng to length of xs
    if lng > 1 then
        set acc to {item 1 of xs}
        repeat with i from 2 to lng
            set acc to acc & {sep, item i of xs}
        end repeat
        if class of xs is string then
            concat(acc)
        else
            acc
        end if
    else
        xs
    end if
end intersperse

-- 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 :: 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

-- unlines :: [String] -> String
on unlines(xs)
    intercalate(linefeed, xs)
end unlines

Or perhaps not. Perhaps words like perhaps suggest this discussion has about run its course :slight_smile:

Agreed - tho I think we can go beyond ‘perhaps’ :slight_smile:

( Passing script-wrapped functions as arguments is the basis of mapping and reducing, both of which protect from endlessly having to hand-write and debug mutating iterations )