17
$\begingroup$

I can't find the link anymore but there was a talk at a Wolfram conference (referencing v7 I think) where the authors highlight that stringing together functions like this:

Range[100] // Partition[#,10]& // Grid[#, Spacings->{1,1}]&

allows you to add arguments to the post-fix function, string together more than one function and reads nicely left-to-right. I have to admit, I find this a nice way to string together functions and wonder why it's not more prevalent. I know there are some subtle precedence issues with post-fix notation so maybe it's just simply more trouble free in the long term if you get used to nesting your functions.

My question: is this just a style issue or is there some valid reason for avoiding this habit early?

$\endgroup$
  • 3
    $\begingroup$ I personally prefer this style and have found no issues with it directly but rather with formatting in the frondend. I like putting each //someFunction on a separate line which is only possible in e.g. Module since otherwise each line gets treated as a separate expression to be evaluated. $\endgroup$ – Sascha May 29 '17 at 7:04
  • 5
    $\begingroup$ @Sascha use Mr.W's way of parenthesizing, as in (expr // someFunc1 // someFunc2 ...), then no module is needed $\endgroup$ – LLlAMnYP May 29 '17 at 7:23
  • $\begingroup$ @LLIAMnYP that is a neat trick, thanks a lot! $\endgroup$ – Sascha May 29 '17 at 7:43
  • 1
    $\begingroup$ Related: mathematica.stackexchange.com/q/139344/26956 mathematica.stackexchange.com/q/110067/26956 Don't think this is opinion-based (at worst, the question can be interpreted as "is this opinion-based or objective). Very useful info on precedence and unexpected effects of composition in the accepted answer. Voting leave open. $\endgroup$ – LLlAMnYP Jun 2 '17 at 10:18
10
$\begingroup$

I use this style sometimes, and I see not issues with it.

You mention precedence. A nice thing about // is that it has very low precedence, even lower than &. Thus it is not necessary to parenthesize the functions.

You can use

f @ (#&) @ arg

with the parentheses, or you can use

 arg // #& // f

without them.

One annoying thing about chaining with // is the auto-indentation done by the front end:

Mathematica graphics

I do not like this. Using RightComposition can alleviate the issue:

Mathematica graphics

This is what this looks like in practice in an example in the IGraph/M documentation:

Mathematica graphics

One thing to watch out for with Composition and RightComposition is that they effectively negate any Hold* attributes of the composed functions:

1 + 1 // Hold // f
(* f[Hold[1 + 1]] *)

1 + 1 // Hold /* f
(* f[Hold[2]] *)
| improve this answer | |
$\endgroup$
6
$\begingroup$

Precedence is only an issue if your input contains a lot of different operators and you aren't aware of their precedence. For example, something like

expr1 /* expr2 // expr4~expr5~expr6 @ expr7

Here you can find a list with the most common operators listed in order of decreasing precedence. This answer contains information on how to find the precedence of an operator and a link to the A New Mathematica Programming Style presentation from the 2007 Wolfram Technology Conference.

The main reason not to use postfix and prefix notation excessively in the final code is performance. Especially when using a lot of computational cheap pure functions, such as in your input example, the introduced overhead is significant:

Table[
  {(Range[100] // Partition[#, 10] & // 
        Grid[#, Spacings -> {1, 1}] & // AbsoluteTiming // 
      First)/(Grid[Partition[Range[100], 10], Spacings -> {1, 1}] // 
       AbsoluteTiming // First),
   (Grid[#, Spacings -> {1, 1}] &@Partition[#, 10] &@Range[100] // 
       AbsoluteTiming // 
      First)/(Grid[Partition[Range[100], 10], Spacings -> {1, 1}] // 
       AbsoluteTiming // First)}
  , 200] // Mean

{1.23117, 1.23615}


I mainly use postfix notation for the output styling part of my code. The personal preferences for different input forms is probably due to one's flow of thinking and programming. f[x] is closer to the mathematical notation $ f(x) $ and f @ x is like saying " f of x" or "f applied to x". Both these notations are very similar in their logic, as they start with the output one wants to get or the thing one wants to do, whereas x // f // g is more inline with a step-by-step approach of "first take/make x, than apply f to it, and finally do g".

| improve this answer | |
$\endgroup$
  • $\begingroup$ It's informative. I have downloaded the Regarding f@arg, it says that it requires some knowledge of the 1000-level Precedence table. It's hard. $\endgroup$ – UnchartedWorks May 29 '17 at 11:24
  • $\begingroup$ +1 for the interesting pick-up about the slower performance of postfix, infix forms compared with circumfix forms but generally I don't find this to be a "significant overhead" or a persuasive reason to avoid reaping the semantic benefits of postfix/prefix notations. I'd venture that the vast majority of WL computations would involve less than 100K examples of the above differential so the relative ~25% performance hit translates to an absolute, imperceptible difference of ~0.06s in practice. Further, any function needing upwards of this many applications should probably be compiled.... $\endgroup$ – Ronald Monson Feb 5 at 21:11
  • $\begingroup$ Having said this, one can also easily envisage say a complex (non-compilable) dataset/database query or number-theoretic function being used upwards of 10M times so the 25% differential then does become significant. At any rate, 25% is 25% (actually in V12 I measure this differential to be closer to 30-35 %) and so it is perhaps a weak argument to say "Oh well people don't do weighty enough computations for this to matter" particularly given the increasing pervasiveness of operator forms that promotes the liberal use of this prefix/postfix coding style. $\endgroup$ – Ronald Monson Feb 5 at 21:21
  • 1
    $\begingroup$ This suggests to me that WRI should probably look at implementing some sort of pre-processing to make this differential disappear. As part of a more general static code profiler this might be low-hanging fruit and ensure that users continue to gain all the readability advantages of operator forms without having to worry about potential performance degradation. $\endgroup$ – Ronald Monson Feb 5 at 21:22
5
$\begingroup$

As you said, there could be some precedence issues. You can use Defer to check if there is any precedence issues. (I tried to use PrecedenceForm to check precedence, however there is an error message found by Karsten 7. So I used Defer instead.

Defer[Range[10] // Partition[#, 10] & // Grid[#, Spacings -> {1, 1}] &]

(*(Grid[#1, Spacings -> {1, 1}] &)[(Partition[#1, 10] &)[Range[10]]]*)

Or you can use ReverseComposition to avoid precedence issues.

In:

ReverseComposition[fs__] := Composition[Sequence @@ Reverse@{fs}]

partition[xs_] := Partition[xs, 10]
grid[xss_] := Grid[xss, Spacings -> {1, 1}]
f = ReverseComposition[Range, partition, grid]
f@100

Out:

grid@*partition@*Range

Mathematica graphics

| improve this answer | |
$\endgroup$
  • 4
    $\begingroup$ Have you heard of RightComposition? ;-) $\endgroup$ – LLlAMnYP May 29 '17 at 7:24
  • $\begingroup$ @LLlAMnYP I forgot it and I reinvented the wheel... :( $\endgroup$ – UnchartedWorks May 29 '17 at 7:26
  • $\begingroup$ @Karsten7. It isn't always like this. if you use //, sometimes you might encounter precedence issues. $\endgroup$ – UnchartedWorks May 29 '17 at 7:55
  • $\begingroup$ @Karsten7. I got it. Thanks! $\endgroup$ – UnchartedWorks May 29 '17 at 8:13
  • 1
    $\begingroup$ The best input I could come up with, that produces an output similar to the one you originally got without using invalid input is: Range[100] // Partition[#, 10] & // Grid[#, Spacings -> {1, 1}] & // Inactivate // Activate[#, Function] & $\endgroup$ – Karsten 7. May 29 '17 at 8:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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