@alldritt’s thoughts in a previous thread:

Using map, filter and reduce or fold in Applescript

reminded me that:

- If I were to add a fourth generic function to the basic
**map****filter fold**toolkit, it would be**concatMap**, and that - I hadn’t mentioned the capture of closure values in the pattern of passing script objects to these functions.

**concatMap** is almost identical to **map**, but it additionally concatenates the list of values produced by mapping.

This allows it to provide a kind of filtering. If a handler or script that is passed to it

- wraps each return value in a list, and
- for some values returns an empty list,

then concatenation eliminates those empty values.

The simplest use would be as a combination of **map** and **filter** - both transforming and occasionally pruning out each element in a list.

If we nest a few concatMaps, passing scripts to them (rather than handlers), and capturing closure values into those scripts, we can use this (*transform + perhaps eliminate*) pattern to write an AppleScript version of a list comprehension.

For example, how many Pythagorean triples can we find using only the integers from 1 to 25 ?

```
-- pythagoreanTriples :: Int -> [(Int, Int, Int)]
on pythagoreanTriples(n)
script x
on |λ|(x)
script y
on |λ|(y)
script z
on |λ|(z)
if x * x + y * y = z * z then
{{x, y, z}}
else
{}
end if
end |λ|
end script
concatMap(z, enumFromTo(1 + y, n))
end |λ|
end script
concatMap(y, enumFromTo(1 + x, n))
end |λ|
end script
concatMap(x, enumFromTo(1, n))
end pythagoreanTriples
-- TEST -----------------------------------------------------------------------
on run
-- Pythagorean triples drawn from integers in the range [1..n]
-- {(x, y, z) | x <- [1..n], y <- [x+1..n], z <- [y+1..n], (x^2 + y^2 = z^2)}
pythagoreanTriples(25)
--> {{3, 4, 5}, {5, 12, 13}, {6, 8, 10}, {7, 24, 25}, {8, 15, 17},
-- {9, 12, 15}, {12, 16, 20}, {15, 20, 25}}
end run
-- GENERIC FUNCTIONS ----------------------------------------------------------
-- concatMap :: (a -> [b]) -> [a] -> [b]
on concatMap(f, xs)
set acc to {}
tell mReturn(f)
repeat with x in xs
set acc to acc & |λ|(contents of x)
end repeat
end tell
return acc
end concatMap
-- enumFromTo :: Int -> Int -> [Int]
on enumFromTo(m, n)
if n < m then
set d to -1
else
set d to 1
end if
set lst to {}
repeat with i from m to n by d
set end of lst to i
end repeat
return lst
end enumFromTo
-- 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
```

We can also combine concatMap with the capture of closure values to define a generic **cartesianProduct** handler.

First a trivial example, and then a more useful one:

**Simple example**

```
on run
cartesianProduct({"Important", "Unimportant"}, {"Urgent", "Not urgent"})
--> {{"Important", "Urgent"}, {"Important", "Not urgent"}, {"Unimportant", "Urgent"}, {"Unimportant", "Not urgent"}}
cartesianProduct({"Alpha", "Beta", "Gamma"}, {1, 2, 3, 4})
--> {{"Alpha", 1}, {"Alpha", 2}, {"Alpha", 3}, {"Alpha", 4}, {"Beta", 1}, {"Beta", 2}, {"Beta", 3}, {"Beta", 4}, {"Gamma", 1}, {"Gamma", 2}, {"Gamma", 3}, {"Gamma", 4}}
end run
-- cartesianProduct :: [a] -> [b] -> [(a, b)]
on cartesianProduct(xs, ys)
script
on |λ|(x)
script
on |λ|(y)
{{x, y}}
end |λ|
end script
concatMap(result, ys)
end |λ|
end script
concatMap(result, xs)
end cartesianProduct
-- concatMap :: (a -> [b]) -> [a] -> [b]
on concatMap(f, xs)
set lng to length of xs
set acc to {}
tell mReturn(f)
repeat with i from 1 to lng
set acc to acc & |λ|(item i of xs, i, xs)
end repeat
end tell
return acc
end concatMap
-- 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
```

**More useful example** (looking at the grid of (clipboard types) * (alternative representations) in the current clipboard)

```
use framework "Foundation"
use framework "Appkit"
use scripting additions
-- INSPECTING DIFFERENT TYPES OF DATA ON THE CLIPBOARD
-- showClipBoard :: () -> Record
on showClipboard()
set ca to current application
set pBoard to ca's NSPasteboard's generalPasteboard
set clipTypes to (item 1 of (pBoard's pasteboardItems as list))'s types as list
set types to {"propertyList", "string", "data"}
script dataFor
on |λ|(accumulator, bundleType)
set {bundleID, k} to bundleType
if k = "string" then
set v to pBoard's stringForType:bundleID
else if k = "propertyList" then
set v to pBoard's propertyListForType:bundleID
else -- "data"
set v to ca's NSString's alloc()'s ¬
initWithData:(pBoard's dataForType:bundleID) ¬
encoding:(ca's NSUTF8StringEncoding)
end if
if v is missing value then
accumulator
else
recordInsert(accumulator, bundleID & " as " & k, v)
end if
end |λ|
end script
foldl(dataFor, {name:""}, cartesianProduct(clipTypes, types))
end showClipboard
-- TEST ------------------------------------------------------------------
on run
showClipboard()
end run
-- GENERIC FUNCTIONS -----------------------------------------------------
-- cartesianProduct :: [a] -> [b] -> [(a, b)]
on cartesianProduct(xs, ys)
script
on |λ|(x)
script
on |λ|(y)
{{x, y}}
end |λ|
end script
concatMap(result, ys)
end |λ|
end script
concatMap(result, xs)
end cartesianProduct
-- concatMap :: (a -> [b]) -> [a] -> [b]
on concatMap(f, xs)
set acc to {}
tell mReturn(f)
repeat with x in xs
set acc to acc & |λ|(contents of x)
end repeat
end tell
return acc
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 |λ|(v, item i of xs, i, xs)
end repeat
return v
end tell
end foldl
-- recordInsert :: Dict -> k -> v -> Dict
on recordInsert(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 recordInsert
-- 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
```

and, of course, the capture of closure values can be useful with any of these functions.

Again, a trivial example, this time with **map**:

```
use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use scripting additions
on run
set fp to filePath("~/Desktop")
script fullPath
on |λ|(x)
fp & "/" & x & ".txt"
end |λ|
end script
map(fullPath, {"alpha", "beta", "gamma"})
end run
-- filePath :: String -> FilePath
on filePath(s)
(stringByStandardizingPath of ¬
(current application's ¬
NSString's stringWithString:(s))) as string
end filePath
-- 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
```