# Function that does for Map what Through does for Apply?

Through allows multiple functions to be easily applied to a single argument. For instance, suppose I wanted to count the number of characters in {"t", "o", "d", "a", "y"}, and also join them into a single string. Then I could do:

s1 = {"t", "o", "d", "a", "y"}
Through[{StringJoin, Length}[#]]& @ s1


{today, 5}

But suppose I wanted to do this for a list of lists. Then I would need to use Map to apply the functions to each sublist. I could accomplish that with this:

s2 = {{"h", "e", "l", "l", "o"}, {"d", "a", "y"}}


{{hello, 5}, {day, 3}}

But that syntax is a bit ugly. It would be simpler and more elegant if there were a function that could do for Map what Through does for Apply, i.e. that allowed one to directly Map multiple functions to a single argument, like so (here I've called the hypothetical function MultiMap):

MultiMap[{StringJoin, Length}[#]]& @ s2


{{hello, 5}, {day, 3}}

Does such a function exist?

• {StringJoin[##], Length[{##}]} & @@@ s2
– Acus
Dec 14, 2021 at 7:41
• @Acus That's nice, and I'll keep it in mind, but it lacks the essential feature of Through, which is that it allows one to apply two different functions to a single argument, rather than having to repeat the argument for each function. Dec 14, 2021 at 8:11
• Through[{StringJoin, Length}[{##}]] & @@@ s2
– Acus
Dec 14, 2021 at 8:38
– Syed
Dec 14, 2021 at 9:28
• Why not just Through[{StringJoin, Length}[#]]&/@s2 ? Dec 21, 2021 at 11:45

ClearAll[mapThrough]
mapThrough[a_List] := Map[Through @ a @ ## &]


or, as suggested by J.M. in comments,

mapThrough[a_] := Map @ Through @* a


or

mapThrough = a |-> Map @ Through @* a


Examples:

s2 = {{"h", "e", "l", "l", "o"}, {"d", "a", "y"}};

mapThrough[{StringJoin, Length}] @ s2

{{"hello", 5}, {"day", 3}}


Use with an arbitrary list of functions:

mapThrough[{StringReverse @* StringJoin, Reverse, First, Last, #[[{1, -1}]&}] @ s2

{{"olleh", {"o", "l", "l", "e", "h"}, "h", "o", {"h", "o"}},
{"yad", {"y", "a", "d"}, "d", "y", {"d", "y"}}}

• I use the following variation myself rather frequently: Through @* {StringJoin, Length} /@ {{"h", "e", "l", "l", "o"}, {"d", "a", "y"}} Dec 14, 2021 at 14:20
• @J.M. I think you should post this as an answer, I really like operator forms that avoid using slots. Dec 14, 2021 at 17:27
• @Carl, this is effectively equivalent to kglr's approach (with the advantage you mention of being slot-free), so I would prefer that kglr edit his answer to include it. Dec 14, 2021 at 17:32
• @J.M. I found all the answers here great—and instructive. But I have to say yours was my favorite, because it accomplishes the task with the simplest possible syntax: Going from "multiApply" to "multiMap" just requires changing from Through[{func1, func2, func3...}[s2]] to Through@*{func1, func2, func3...}/@s2. And it cleverly accomplishes this by composing func 1, func2,.. with Throughto create a new composite function, and then mapping that composite function to the arguments. Since you donated your syntax to kglr's answer, I'll give it the credit :). Dec 17, 2021 at 5:45

One option is to use the forking operator {fn1, fn2, ...} from the Query sublanguage:

Query[All, {StringJoin, Length}][s2]

(* {{hello,5},{day,3}} *)


First, the shortcut @@@ is equivalent to "Map Apply" or simply speaking apply to level 1:

Apply[f, {{1, 2}, {a, b}}, {1}]


{f[1, 2], f[a, b]}

f@@@{{1, 2}, {a, b}}


{f[1, 2], f[a, b]}

Second, {##} takes all arguments and puts into List on which then pure function f = Through[{StringJoin, Length}[#]]& is applied. In general you can imagine functional programming like an engineering line conveyor which produces something you want from elementary operations.

Use the operator form of Map and new-in-14.0 Comap:

Map[Comap@{StringJoin, Length}]@s2
(* {{"hello", 5}, {"day", 3}} *)