2

As far as I am aware, aside from simplification, everything that can be done with mapply can be done with Map. After all, Map is a wrapper for mapply. However, I was surprised to see that mapply takes both a ... set of arguments (which the docs call "arguments to vectorize over (vectors or lists of strictly positive length, or all of zero length)") and a MoreArgs argument on top of the required function f, whereas Map does not use MoreArgs, only needing ... (which the docs just call "vectors") and f.

My question is this: Why does mapply need MoreArgs but Map doesn't? Can mapply do something that Map cannot? Or is mapply trying to make something easier that would be harder with Map? And if so, what?

I suspect that sapply may be a useful point of reference for an answer. It may be helpful to compare its X, FUN and ... arguments to mapply's ... and MoreArgs.

J. Mini
  • 1,868
  • 1
  • 9
  • 38
  • I think the first answer of this https://stackoverflow.com/questions/3505701/grouping-functions-tapply-by-aggregate-and-the-apply-family will help you. – Jonas Feb 08 '21 at 16:24

1 Answers1

6

Let's look at the code of Map :

Map
function (f, ...) 
{
    f <- match.fun(f)
    mapply(FUN = f, ..., SIMPLIFY = FALSE)
}

As you wrote, it's just a wrapper, and the dots are forwarded to mapply, note that SIMPLIFY is hardcoded to FALSE

Why does mapply need MoreArgs but Map doesn't?

It's a design choice, possibly due in part to historical reasons, I wouldn't have minded explicit MoreArgs and USE.NAMES arguments (or a SIMPLIFY = TRUE argument for that matter), but I believe the rationale is that Map is meant to be simple, if you want to tweak parameters you're encouraged to use mapply. Nevertheless, you can use MoreArgs and USE.NAMES with Map, they will travel through the dots to the mapply call, though it is undocumented, as the doc describes the ... argument as "vectors".

Map(sum, setNames(1:2, c("a", "b")), 1:2)
#> $a
#> [1] 2
#> 
#> $b
#> [1] 4

Map(sum, setNames(1:2, c("a", "b")), 1:2, USE.NAMES = FALSE)
#> [[1]]
#> [1] 2
#> 
#> [[2]]
#> [1] 4

Map(
  replicate, 
  2:3,
  c(FALSE, TRUE),
  MoreArgs = list(expr = quote(runif(1))))
#> [[1]]
#> [[1]][[1]]
#> [1] 0.7523955
#> 
#> [[1]][[2]]
#> [1] 0.4922519
#> 
#> 
#> [[2]]
#> [1] 0.81626690 0.07415023 0.56264388

The equivalent mapply calls would be :

mapply(sum, setNames(1:2, c("a", "b")), 1:2, SIMPLIFY = FALSE)
#> $a
#> [1] 2
#> 
#> $b
#> [1] 4

mapply(sum, setNames(1:2, c("a", "b")), 1:2, USE.NAMES = FALSE, SIMPLIFY = FALSE)
#> [[1]]
#> [1] 2
#> 
#> [[2]]
#> [1] 4

mapply(
  replicate,
  2:3,
  c(FALSE, TRUE),
  MoreArgs = list(expr = quote(runif(1))))
#> [[1]]
#> [[1]][[1]]
#> [1] 0.6690229
#> 
#> [[1]][[2]]
#> [1] 0.7529774
#> 
#> 
#> [[2]]
#> [1] 0.8632736 0.7822639 0.8553680

Can mapply do something that Map cannot? Or is mapply trying to make something easier that would be harder with Map? And if so, what?

You cannot use SIMPLIFY with Map:

Map(sum, 1:3, 1:3, SIMPLIFY = TRUE)
#> Error in mapply(FUN = f, ..., SIMPLIFY = FALSE): formal argument "SIMPLIFY" matched by multiple actual arguments

a bit of history

  • mapply was introduced with R 1.7.0
  • Map was introduced with R 2.6, the NEWS item reads :

New higher-order functions Reduce(), Filter() and Map().

The f argument name is shared among those functions and they are documented on the same page. The reason why moving away from naming functions FUN is unknown to me, but the consistency between these 3 functions (and the other functions documented in ?Reduce) explains why mapply and Map don't name their function argument the same way (the consistency explains the upper case M too I guess).

In the doc we can also read :

Map is a simple wrapper to mapply which does not attempt to simplify the result, similar to Common Lisp's mapcar (with arguments being recycled, however). Future versions may allow some control of the result type.

So, in theory, as I understand from the last sentence, Map could be upgraded to provide some type stability, similar to what vapply does. It seems to me that R-devel didn't go all the way because they wanted to take time to decide properly what to do, and they left it in this state since then.

moodymudskipper
  • 46,417
  • 11
  • 121
  • 167
  • Did you mean to pass MoreArgs to Map in your second example? It's good for showing that you can pass that argument to Map's mapply, but you've not shown if/how Map can do what's mapply MoreArgs does without the undocumented trick of passing MoreArgs on to mapply. The question contained the premise that, aside from simplification, Map doesn't need MoreArgs to do all that mapply does. Is that premise false? That is, do you need to use mapply's MoreArgs through Map to have Map do all that mapply can? – J. Mini Feb 10 '21 at 21:23
  • I did say "I believe the rationale is that Map is meant to be simple, if you want to tweak parameters you're encouraged to use mapply", I thought it answered this. `Map` is indeed meant to be a wrapper around `mapply` with limited scope, you can do all `Map` does with `mapply` alone, but the opposite isn't true. – moodymudskipper Feb 10 '21 at 22:10
  • I'm still having doubts about these examples. Your second use of `Map`, i.e. `Map(sum, setNames(1:2, c("a", "b")), 1:2, moreArgs = 10, USE.NAMES = FALSE)` doesn't need the `MoreArgs` argument. `Map(sum, setNames(1:2, c("a", "b")), 1:2, 10, USE.NAMES = FALSE)` gives the exact same output, suggesting that the distinction between `Map`'s `...` and `mapply`'s `MoreArgs` has not yet been made clear. – J. Mini Feb 14 '21 at 17:46
  • I added an example – moodymudskipper Feb 15 '21 at 15:40
  • Thanks. Last comment from me: Is there anything to be said for how an equivalent way to write your new example would be `Map(function(x,y) replicate(x,runif(1),y),2:3,c(FALSE, TRUE))`? It's clear that `MoreArgs` is intended to be the parameters that are used in every call to `mapply`. That is, they're not vecotrized over. Does this mean that we can call `MoreArgs` some sort of syntactic sugar? Given your answer, I'm trying to answer the question of what a `Map` user is intended to use in place of the undocumented `MoreArgs`. – J. Mini Feb 15 '21 at 18:03
  • In R there are often many ways to write something, for instance you could write `lapply(lst , mean, na.rm = TRUE)`, or `lapply(lst, function(x) mean(x, na.rm = TRUE)`. I don't know if I'd call it syntactic sugar, but I don't know if there's a canonical definition of the term so might call it that :). – moodymudskipper Feb 16 '21 at 10:01
  • A `Map` user is intended to use `mapply` with `SIMPLIFY = FALSE` instead whenever the documented behavior of `Map` is not suitable. – moodymudskipper Feb 16 '21 at 10:04