42
$\begingroup$

Why does the pure function # @ #2 & have no name?

I'm asking because sometimes I find inelegant to write expressions like

MapThread[#@#2 &, {{a, b, c}, {1, 2, 3}}]
{a[1], b[2], c[3]}

I would rather have a name form such as

MapThread[Work, {{a, b, c}, {1, 2, 3}}] 
$\endgroup$
9
  • 7
    $\begingroup$ There is, but it is deprecated: Compose. You can also use Composition[#1][#2] &, although this is hardly better. I still use Compose myself, but I would not take the responsibility to recommend this as a common practice. $\endgroup$ Commented Jul 18, 2013 at 12:11
  • $\begingroup$ @LeonidShifrin Oh, it is indeed. I haven't thought because I thought that it works like Composition and Composition does not work like I wanted $\endgroup$
    – Kuba
    Commented Jul 18, 2013 at 12:13
  • 1
    $\begingroup$ @LeonidShifrin I think You should post this as an answer since documentations says "Compose has been superseded by Composition" only. What could be misleading $\endgroup$
    – Kuba
    Commented Jul 18, 2013 at 12:15
  • 6
    $\begingroup$ Just so novices won't be confused here, I'd like to add that nothings preventing anyone from just defining Work=#1@#2& or similar. Also in your example you can use MapAll in place of Work, but that's just a happy coincidence and will break as soon as you do anything slightly different. :P $\endgroup$
    – jVincent
    Commented Jul 18, 2013 at 12:21
  • $\begingroup$ @Kuba Ok, done. $\endgroup$ Commented Jul 18, 2013 at 12:23

3 Answers 3

48
$\begingroup$

Compose and Composition

There is, but it is deprecated (in favor of Composition): Compose:

MapThread[Compose, {{a, b, c}, {1, 2, 3}}]

(* {a[1], b[2], c[3]} *)

I still use Compose myself, but I would not take the responsibility to recommend this as a common practice. You can also use Composition[#1][#2] &, although this is hardly better than your original suggestion in terms of code brevity.


Composition is more general because the result of Composition is a function which can take several arguments:

Composition[f, h][x, y]

(* f[h[x, y]] *)

Composition also has a Flat attribute, which is a mixed blessing (see e.g. discussion in this answer, where this leads to a huge slow-down for iteratively constructed composition of functions).

Compose and Composition vs. Function - a clarification

Finally, there was a question in the comments why one can not use Function in a similar manner, since one may get an impression that Function is also constructing function calls. Actually, this is not quite so.

Compose is used basically to construct the square brackets, which is syntactically (and also semantically) non-trivial operation. One doesn't have to tie that to functions - one can think of Compose as a tool for programmatic building of normal expressions with non-trivial heads (the same is also true for Composition). So, where we would type something like

f[a]

or

f @ a

we can now do that programmatically as

Compose[f, a]

or similarly with Composition. This is a non-trivial capability, and it has to do with our ability to programmatically construct normal expressions from symbols / other normal expressions. For example, consider the following expression:

expr = Sin[x + Cos[y*z]];

We can get the symbols it is built with:

syms = Cases[expr, _Symbol, Infinity, Heads -> True]

(* {Sin, Plus, x, Cos, Times, y, z} *)

Here is how one can reconstruct it from symbols, using only Composition[..][..]:

Composition[Sin, Plus][x, Composition[Cos, Times][y, z]]

(* Sin[x + Cos[y z]] *)

Of course, built-in Composition itself is not that magical, and one can write their own version of Composition using e.g. replacement rules (and the same is true for Compose). But it is important to recognize their conceptual significance as functions which encapsulate programmatic expression-building. And they could not care less whether expressions they build are executable code (evaluate non-trivially), or just inert symbolic trees.

Now, Function serves a different purpose - it allows to construct function calls programmatically by generating a function call code from a function (basically a macro with placeholders) and a sequence of arguments at run-time. When we define a function like

plus = Function[#1 + #2]

we in fact define a macro which substitutes the parameters of the actual function call like

plus[1, 2]

(* 3  *)

into the body #1+#2 and only then evaluates the body.

So Function has to use lazy evaluation, to allow us to separate the process of defining a function expression, from calling that function with some arguments. And, in terms of execution time, it allows one to postpone the evaluation from "definition-time" to run-time (thus Function is HoldAll).

This is a different purpose from that of Compose and Composition (which, for example, don't carry Hold*-attributes, because they don't have to prevent any evaluation). By itself, Function is not able to syntactically construct an expression from its head and elements. And MapThread[f,{{a,b},{x,y}}] will return {f[a,x],f[b,y]} for a generic f. Therefore, using Function in MapThread will be no different from any other head, which is what one can observe when substituting Function into MapThread in the original example. Put in other way, Function takes care of slots and the ampersand in #1@#2&, but not of @, which is the important thing here.

$\endgroup$
10
  • $\begingroup$ This is the first case when I do not see any reason to wait with accept :) But I will to be fair :) Thank You. $\endgroup$
    – Kuba
    Commented Jul 18, 2013 at 12:26
  • $\begingroup$ @Kuba No problem :). This question was destined to emerge sooner or later. $\endgroup$ Commented Jul 18, 2013 at 12:28
  • $\begingroup$ @LeonidShifrin chapeau again and again (+1)...i have still one question, if we say that #@#2& is short for Function[#1[#2]] then i don't see why: MapThread[Function, {{a,b,c},{1,2,3}}] shouldn't work...the intention is clear. $\endgroup$
    – Stefan
    Commented Jul 18, 2013 at 12:37
  • 1
    $\begingroup$ @Stefan ... Therefore, using Function in MapThread will be no different from any other head, which is what you can observe when substituting Function into MapThread in the original example. Put in other way, Function takes care of slots and the ampersand in #1@#2&, but not of @, which is the important thing here. $\endgroup$ Commented Jul 18, 2013 at 12:50
  • 1
    $\begingroup$ @Stefan Yes, Function is used to delay evaluation, basically as a run-time macro with delayed evaluation, but not to construct expressions from pieces. I guess this is a Mathematica - specific issue and may be unintuitive when viewed from viewpoints of some other languages. In a sense, there are no functions in Mathematica, just rules and evaluation procedure. $\endgroup$ Commented Jul 18, 2013 at 13:07
14
$\begingroup$

Its Name Is Construct

The built-in function Construct has been introduced in v11.3. The original question was asked and answered in 2013, prior to the release of v11.3 and the introduction of Construct.

Construct[f, x]
f[x]
Construct[f, x, y, z]
f[x, y, z]

Construct does precisely what question seems to be looking for:

MapThread[Construct, {{a, b, c}, {1, 2, 3}}]
{a[1], b[2], c[3]}

I argue that neither Compose nor Composition is the correct answer to the question as asked. Note that while Composition can sometimes replace Construct in specific circumstances (like the last example), Composition composes functions resulting in a new function that can then be applied to arguments, while Construct actually applies the function to the argument(s).

Composition[f, g]
f@*g
Construct[f, g][x]
f[g][x]

Unfortunate Naming Conventions

The name of Construct is surely meant to evoke the cons function from Lisp which takes two arguments and constructs a list with the first argument as the head (car) and the second argument as the tail (cdr). (A "constructor" in object-oriented languages also shares this etymology.) The named function fills a gap in Wolfram Language: Prior to Construct the square brackets notation was nameless.

On the other hand, the "names" for the other function application operators do not correspond to the functions that define them. For example, @ is named Prefix (by Precedence, for example), but the Prefix function merely affects the display of a function that is already applied. Likewise, ~ is named Infix, and // is named Postfix, but neither corresponds to the functions of the same name. Maybe they should be named PrefixConstruct, InfixConstruct, and PostfixConstruct. Alas, they didn't consult me before naming them.

$\endgroup$
4
  • 4
    $\begingroup$ This is the correct answer. The name that the original poster is looking for is Construct, not Compose. $\endgroup$ Commented Oct 15, 2018 at 5:00
  • $\begingroup$ @Shredderroy and Robert, Construct clearly meets my original needs. But I don't see how is it better than Compose. And given it is a way older symbol I find it more "correct". I know Composition is out of the question but there is nothing that explains why you'd argue about Compose. Or did you mean it is better with respect to naming conventions / consistency? $\endgroup$
    – Kuba
    Commented Oct 22, 2018 at 21:33
  • 2
    $\begingroup$ @Kuba in my opinion, it has more to do with the semantics of the two calls. Even if we were to look past the fact that Compose is deprecated, the admissibility of Compose in this case is due to the fact that the resulting symbolic form just happens to resolve in DownValues. But Construct has the very semantics that you seek in your original question, in that it invokes the first symbol with the second as its parameter. (On a side note, I don't necessarily think of older functionality as more correct.) $\endgroup$ Commented Oct 22, 2018 at 22:42
  • $\begingroup$ @Kuba I am in agreement with @Shredderroy. But if you give preference to much older functions, why would you prefer Compose over HeadCompose? The latter seems to me to be closer to your intention, IMHO, and for a similar reason to why I think Construct is the "right" answer today. (But we are splitting hairs, here.) $\endgroup$ Commented Oct 24, 2018 at 3:35
3
$\begingroup$

Another important role played by pure functions (those built with slots #) is that in functional programming (and Mathematica follows that paradigm) many times you need to apply a function that doesn't live outside the specific place where you define/evaluate it. In other words, mapping a function to a data set can be done without to save any unneeded variable/function in memory, just using a pure function.

Names["Global`*"]
{}

MapThread[#@#2 &, {{a, b, c}, {1, 2, 3}}]
{a[1], b[2], c[3]}

Names["Global`*"]
{"a", "b", "c"}

f[letter_, number_] := letter[number];
MapThread[f[#1, #2] &, {{a, b, c}, {1, 2, 3}}]
{a[1], b[2], c[3]}

same result but now you have many more variable in the memory and they are not needed outside the MapThread itseldf, so the memory used is waste.

Names["Global`*"]
{"a", "b", "c", "f", "letter", "number"}

For that using pure the variables have no names.

$\endgroup$
3
  • $\begingroup$ This is correct, but does not seem very relevant to the question being discussed. The question seems to be about a very particular construction, and my digression into some particular aspects of pure functions in my answer was motivated by the comments rather than the original question itself. If your answer is a kind of a reply to that part of my answer, then I hasten to comment that I did not intend to describe pure functions in any extensive manner there, but only considered one particular aspect of them, relevant to the original question and answering the questions raised in comments. $\endgroup$ Commented Jul 18, 2013 at 18:17
  • $\begingroup$ Leonid thanks for your comment. I'm new on SE and I'm learning how it works. I thought your answer, while correct, was not fully centered on the question. Indeed, the question I tried to answer is "Why there is no name for #1@#2&". The use of Compose/Composition is something about a possible alternative to use the pure function, not explaining why pure functions don't use variable's name. At least, this is my interpretation of the question. If I misinterpreted the question and you think my answer is not relevant for the whole thread, I can remove my answer. $\endgroup$
    – bobknight
    Commented Jul 19, 2013 at 6:45
  • $\begingroup$ @bobknight You have convinced me that it is possible to interprete my question this way. However, as Leonid Shifrin has said, it was about very particular construction, that's why I haven't precised it better. Maybe I should edit the title to: "Is there a built-in function identical to pure #@#2&?" ? $\endgroup$
    – Kuba
    Commented Jul 19, 2013 at 8:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.