8

I'm in love with Ruby. In this language all core functions are actually methods. That's why I prefer postfix notation – when the data, which I want to process is placed left from the body of anonymous processing function, for example: array.map{...}. I believe, that it has advantages in how easy is this code to read.

But Mathetica, being functional (yeah, it can be procedural if you want) dictates a style, where Function name is placed left from the data. As we can see in its manuals, // is used only when it's some simple Function, without arguments, like list // MatrixForm. When Function needs a lot of arguments, people who wrote manuals, use syntax F[data].
It would be okay, but my problem is the case F[f,data], for example Do[function, {x, a, b}]. Most of Mathematica functions (if not all) have arguments in exactly this order – [function, data], not [data, function]. As I prefer to use pure functions to keep namespace clean instead of creating a lot of named functions in my notebook, the argument function can be too big – so big, that argument data would be placed on the 5-20th line of code after the line with Function call.

This is why sometimes, when evil Ruby nature takes me under control, I rewrite such functions in postfix way:

Do[f (x), {x, a, b}]\n{x, a, b} // Do[f (x), #] &

Because it's important for me, that pure function (potentially big code) is placed right from processing data. Yeah I do it and I'm happy. But there are two things:

  1. this causes Mathematica's highlighting parser problem: the x in postfix notation is highlighted with blue color, not turquoise;
  2. everytime when I look into Mathematica manuals, I see examples like this one: Do[x[[i]] = (v[[i]] - U[[i, i + 1 ;; n]].x[[i + 1 ;; n]])/ U[[i, i]], {i, n, 1, -1}];, which means... hell, they think it's easy to read/support/etc.?!

So these two things made me ask this question here: am I so bad boy, that use my Ruby-style, and should I write code like these guys do, or is it OK, and I don't have to worry, and should write as I like to?

Community
  • 1
  • 1
Nakilon
  • 34,866
  • 14
  • 107
  • 142
  • 1
    I think you may be interested in a presentation ["Mathematica and Programming Style. A New Mathematica Programming Style" by Kris Carlson](http://library.wolfram.com/infocenter/Conferences/7002/) which is along the lines of the question (thanks @TomD for the reference). – Alexey Popkov Aug 19 '11 at 09:01
  • rcollyer's, acl's and Mr.Wizard's answers were the most on-topic. Thanks to all for explaining possible issue. – Nakilon Aug 23 '11 at 00:46

6 Answers6

10

The style you propose is frequently possible, but is inadvisable in the case of Do. The problem is that Do has the attribute HoldAll. This is important because the loop variable (x in the example) must remain unevaluated and be treated as a local variable. To see this, try evaluating these expressions:

x = 123;

Do[Print[x], {x, 1, 2}]
(* prints 1 and 2 *)

{x, 1, 2} // Do[Print[x], #]&
(* error: Do::itraw: Raw object 123 cannot be used as an iterator.
   Do[Print[x], {123, 1, 2}]
*)

The error occurs because the pure function Do[Print[x], #]& lacks the HoldAll attribute, causing {x, 1, 2} to be evaluated. You could solve the problem by explicitly defining a pure function with the HoldAll attribute, thus:

{x, 1, 2} // Function[Null, Do[Print[x], #], HoldAll]

... but I suspect that the cure is worse than the disease :)

Thus, when one is using "binding" expressions like Do, Table, Module and so on, it is safest to conform with the herd.

WReach
  • 18,098
  • 3
  • 49
  • 93
8

I think you need to learn to use the styles that Mathematica most naturally supports. Certainly there is more than one way, and my code does not look like everyone else's. Nevertheless, if you continue to try to beat Mathematica syntax into your own preconceived style, based on a different language, I foresee nothing but continued frustration for you.

Whitespace is not evil, and you can easily add line breaks to separate long arguments:

Do[
  x[[i]] = (v[[i]] - U[[i, i + 1 ;; n]].x[[i + 1 ;; n]]) / U[[i, i]]
  , {i, n, 1, -1}
];

This said, I like to write using more prefix (f @ x) and infix (x ~ f ~ y) notation that I usually see, and I find this valuable because it is easy to determine that such functions are receiving one and two arguments respectively. This is somewhat nonstandard, but I do not think it is kicking over the traces of Mathematica syntax. Rather, I see it as using the syntax to advantage. Sometimes this causes syntax highlighting to fail, but I can live with that:

f[x] ~Do~ {x, 2, 5} 

When using anything besides the standard form of f[x, y, z] (with line breaks as needed), you must be more careful of evaluation order, and IMHO, readability can suffer. Consider this contrived example:

{x, y} // # + 1 & @@ # &

I do not find this intuitive. Yes, for someone intimate with Mathematica's order of operations, it is readable, but I believe it does not improve clarity. I tend to reserve // postfix for named functions where reading is natural:

Do[f[x], {x, 10000}] //Timing //First
Mr.Wizard
  • 24,179
  • 5
  • 44
  • 125
7

I'd say it is one of the biggest mistakes to try program in a language B in ways idiomatic for a language A, only because you happen to know the latter well and like it. There is nothing wrong in borrowing idioms, but you have to make sure to understand the second language well enough so that you know why other people use it the way they do.

In the particular case of your example, and generally, I want to draw attention to a few things others did not mention. First, Do is a scoping construct which uses dynamic scoping to localize its iterator symbols. Therefore, you have:

In[4]:= 
x=1;
{x,1,5}//Do[f[x],#]&

During evaluation of In[4]:= Do::itraw: Raw object 
1 cannot be used as an iterator. >>

Out[5]= Do[f[x],{1,1,5}]

What a surprise, isn't it. This won't happen when you use Do in a standard fashion.

Second, note that, while this fact is largely ignored, f[#]&[arg] is NOT always the same as f[arg]. Example:

ClearAll[f];
SetAttributes[f, HoldAll];
f[x_] := Print[Unevaluated[x]]

f[5^2]

5^2

f[#] &[5^2]

25

This does not affect your example, but your usage is close enough to those cases affected by this, since you manipulate the scopes.

Leonid Shifrin
  • 22,449
  • 4
  • 68
  • 100
  • +1 as these are good points. I doubt anybody who knew enough mma programming to attempt to do something like your `Unevaluated` example would have much trouble working out what happened, though. Which of course is the point you start out by making: do whatever you want as long as you know what you're doing. – acl Aug 17 '11 at 16:28
  • 1
    @acl There are real examples without `Unevaluated` that are important and do confuse people. For example: `x=1;With[{x = Pi}, #]&[Sin[x]]`. – Leonid Shifrin Aug 17 '11 at 16:31
  • 1
    That doesn't surprise me. I'd expect `Sin[x]` to be evaluated prior to being passed into `With` because the form `f[#]&[x]` applies any properties of `f` to `#` not `x`, or more succinctly, `x` is a parameter of `f[#]&` not `f`. – rcollyer Aug 17 '11 at 16:36
  • @rcollyer Not everybody is so advanced. A large number of people get surprised by this, and the problem is made worse by the documentation claiming the equivalence of `f[x]` and `f[#]&[x]`. – Leonid Shifrin Aug 17 '11 at 16:39
  • I agree about the documentation. I'll admit, it startled me the first time I ran across it. But, I realized it made sense in terms of execution order. What this really says is by using a form other than the standard form you give up some control, and as long as you understand that, there shouldn't be a problem. – rcollyer Aug 17 '11 at 17:14
  • @rcollyer By the way, the same happens with `Apply`, and may look even more confusing for the uninitiated: `With@@{{x = Pi},Sin[x]}`, since no explicit parameter-passing takes place. Regarding the initial one with `#-&`, there are cases where this also is the cause of the problem but it is not as obvious as here. I remember answering some Mathgroup question containing such example, but can't recall what the question was. – Leonid Shifrin Aug 17 '11 at 17:16
  • Hadn't thought of that, although `x=1; With@@{{x = Pi},Sin[x]}` generates an error. But, not the one I expected. I expected issues with setting `1==Pi`, but it appears to drop the `Set` entirely leaving `{Pi}` as the variable specification. (Most likely has to do with how `With` interprets the variable list.) The error I expected, I think, would confuse a newer user less as it would lead them to the correct conclusion - use standard form - more than the error it does emit. – rcollyer Aug 17 '11 at 17:26
  • My point actually wasn't that most people are advanced enough to not be surprised, but rather that most people who'd play with this sort of thing would have little trouble working out what happened if surprised. But as I said your point about evaluation orders (and also Wreach's point) are both excellent. – acl Aug 17 '11 at 17:57
  • @rcollyer (I tried posting this comment twice, hope it does not turn up twice) If you trace it, it evaluates the `{{},}` structure first, obtaining {{Pi},0}, and then applies `With` to that. Presumably the reason you don't get `1=Pi` is that `Set` has `HoldFirst`. Although I guess if you got confused with this, it sort of proves that even advanced users aren't immune (and/or that mma's evaluation sequence can quickly become baroque). – acl Aug 17 '11 at 18:11
  • @acl, I thought the error was odd and unexpected. Also, why would `HoldFirst` cause that behavior? – rcollyer Aug 17 '11 at 18:43
  • @rcollyer I simply meant that eg `x=1;Plus[x,2]` evaluates to 3, as `x` is evaluated to `1` and then added to 2, while `x=1;Set[x,2]` holds `x` and then assigns `2` to `x`, rather than attempting to assign to `1`. Or at least that is my mental model of it and it appears to work; if it doesn't, I can always trace what is happening and try to find out, which is all I was really trying to say in these comments... – acl Aug 17 '11 at 19:07
  • 1
    @rcollyer I can confirm @acl's conclusion: `Set` indeed does its work fine, due to `HoldFirst`, and the result (`Pi`) is what `With` sees when the evaluation comes to it (`Set` returns back the result of the evaluation of the r.h.s.). It would be completely equivalent to use this code: `x = 1; x = Pi; With@@{{Pi},Sin[x]}`. – Leonid Shifrin Aug 17 '11 at 19:14
6

Mathematica supports 4 ways of applying a function to its arguments:

  1. standard function form: f[x]
  2. prefix: f@x or g@@{x,y}
  3. postfix: x // f, and
  4. infix: x~g~y which is equivalent to g[x,y].

What form you choose to use is up to you, and is often an aesthetic choice, more than anything else. Internally, f@x is interpreted as f[x]. Personally, I primarily use postfix, like you, because I view each function in the chain as a transformation, and it is easier to string multiple transformations together like that. That said, my code will be littered with both the standard form and prefix form mostly depending on whim, but I tend to use standard form more as it evokes a feeling of containment with regards to the functions parameters.

I took a little liberty with the prefix form, as I included the shorthand form of Apply (@@) alongside Prefix (@). Of the built in commands, only the standard form, infix form, and Apply allow you easily pass more than one variable to your function without additional work. Apply (e.g. g @@ {x,y}) works by replacing the Head of the expression ({x,y}) with the function, in effect evaluating the function with multiple variables (g@@{x,y} == g[x,y]).

The method I use to pass multiple variables to my functions using the postfix form is via lists. This necessitates a little more work as I have to write

{x,y} // f[ #[[1]], #[[2]] ]&

to specify which element of the List corresponds to the appropriate parameter. I tend to do this, but you could combine this with Apply like

{x,y} // f @@ #&

which involves less typing, but could be more difficult to interpret when you read it later.

Edit: I should point out that f and g above are just placeholders, they can, and often are, replaced with pure functions, e.g. #+1& @ x is mostly equivalent to #+1&[x], see Leonid's answer.

To clarify, per Leonid's answer, the equivalence between f@expr and f[expr] is true if f does not posses an attribute that would prevent the expression, expr, from being evaluated before being passed to f. For instance, one of the Attributes of Do is HoldAll which allows it to act as a scoping construct which allows its parameters to be evaluated internally without undo outside influence. The point is expr will be evaluated prior to it being passed to f, so if you need it to remain unevaluated, extra care must be taken, like creating a pure function with a Hold style attribute.

Community
  • 1
  • 1
rcollyer
  • 10,475
  • 4
  • 48
  • 75
5

You can certainly do it, as you evidently know. Personally, I would not worry about how the manuals write code, and just write it the way I find natural and memorable.

However, I have noticed that I usually fall into definite patterns. For instance, if I produce a list after some computation and incidentally plot it to make sure it's what I expected, I usually do

prodListAfterLongComputation[
    args,
]//ListPlot[#,PlotRange->Full]&

If I have a list, say lst, and I am now focusing on producing a complicated plot, I'll do

ListPlot[
    lst,
    Option1->Setting1,
    Option2->Setting2
]

So basically, anything that is incidental and perhaps not important to be readable (I don't need to be able to instantaneously parse the first ListPlot as it's not the point of that bit of code) ends up being postfix, to avoid disrupting the already-written complicated code it is applied to. Conversely, complicated code I tend to write in the way I find easiest to parse later, which, in my case, is something like

f[
    g[
        a,
        b,
        c
    ]
]

even though it takes more typing and, if one does not use the Workbench/Eclipse plugin, makes it more work to reorganize code.

So I suppose I'd answer your question with "do whatever is most convenient after taking into account the possible need for readability and the possible loss of convenience such as code highlighting, extra work to refactor code etc".

Of course all this applies if you're the only one working with some piece of code; if there are others, it is a different question alltogether.

But this is just an opinion. I doubt it's possible for anybody to offer more than this.

acl
  • 6,490
  • 1
  • 27
  • 33
  • I use the same layout as you, but do you really think it's more work to reorganize code? I find mma indents perfectly when you break a line using return. So, all I have to do is enter a few returns and I'm done. As an added bonus, if formatting acts funny, that's most often a cue for some kind of syntax error. – Sjoerd C. de Vries Aug 17 '11 at 18:40
  • @Sjoerd I agree with your comments, and it is particularly nice that you can redefine the indentation behaviour (and I have palettes to let me change that on the fly). But as I use both the fronted and text editors, I don't rely on on-the-fly indentation but put it in by hand, and switch off the automatic stuff. Then I can structure everything however I want, but have no tools to increase/decrease indentation level (say). In any case, I may not be expressing it very well, but I find the experience of coding in emacs or my python IDE much more pleasant in how efficiently I can structure my code – acl Aug 17 '11 at 19:13
  • (cont'd) Of course, the frontend has the advantage of being programmable in mathematica, but I find that I usually just use workbench instead for anything longish. In fact sometimes I write code in emacs in a terminal and, although code there is "inert", it is in some ways more easily malleable than in the frontend. But anyway these are tastes. – acl Aug 17 '11 at 19:16
  • What about syntax coloring then? – Sjoerd C. de Vries Aug 17 '11 at 21:55
  • @Sjoerd what about it? I don't understand the question... Unless you mean about eg emacs, to which the answer would be a) there's a mode to syntax color mma code for emacs (of course!), b) I don't prefer it to the frontend or workbench, I just find a few aspects of it more pleasant (eg the ability to increase indentation of a block easily). But if I am using one of the powerful machines at work from home via ssh, i am forced to either edit mma code via emacs in the terminal, scp things nonstop, or type flawless code the first time :) – acl Aug 17 '11 at 22:11
  • Well I suppose emacs doesn't know all `Names[]` or does it? Does it have name completion? BTW can't you work with a remote kernel from your local frontend? – Sjoerd C. de Vries Aug 17 '11 at 22:22
  • @Sjoerd certainly emacs is nowhere near as good for mathematica programming as the frontend; no argument templates, none of the dynamic features, nothing. even the workbench isn't as dynamic. I do not use a remote kernel with the frontend on my laptop but a) it seems to work or not randomly and I have wasted enough time trying to fix it already, b) the connection is way too slow, c) generally I develop on my laptop and only run heavy jobs on one of the larger machines at work (some have 512GB memory, some more...), so it doesn't really matter: I use emacs for quick edits mostly. – acl Aug 17 '11 at 22:31
1

For one-argument functions (f@(arg)), ((arg)//f) and f[arg] are completely equivalent even in the sense of applying of attributes of f. In the case of multi-argument functions one may write f@Sequence[args] or Sequence[args]//f with the same effect:

In[1]:= SetAttributes[f,HoldAll];
In[2]:= arg1:=Print[];
In[3]:= f@arg1
Out[3]= f[arg1]
In[4]:= f@Sequence[arg1,arg1]
Out[4]= f[arg1,arg1]

So it seems that the solution for anyone who likes postfix notation is to use Sequence:

x=123;
Sequence[Print[x],{x,1,2}]//Do
(* prints 1 and 2 *)

Some difficulties can potentially appear with functions having attribute SequenceHold or HoldAllComplete:

In[18]:= Select[{#, ToExpression[#, InputForm, Attributes]} & /@ 
   Names["System`*"], 
  MemberQ[#[[2]], SequenceHold | HoldAllComplete] &][[All, 1]]

Out[18]= {"AbsoluteTiming", "DebugTag", "EvaluationObject", \
"HoldComplete", "InterpretationBox", "MakeBoxes", "ParallelEvaluate", \
"ParallelSubmit", "Parenthesize", "PreemptProtect", "Rule", \
"RuleDelayed", "Set", "SetDelayed", "SystemException", "TagSet", \
"TagSetDelayed", "Timing", "Unevaluated", "UpSet", "UpSetDelayed"}
Alexey Popkov
  • 9,355
  • 4
  • 42
  • 93
  • 1
    The statement of complete equivalence holds only for stand-alone function calls. Within a larger expression, these forms are not completely equivalent since they have different precedence levels. Using `Sequence` is not the same as pure function calls because sequence - splicing is a run - time rather than parse-time effect. Apart from the possible issues you mentioned, `Sequence` - splicing induces non-zero run-time overhead. I would not use it in this context. I am sure you are aware of all that, so the comment is intended for the readers of your answer. – Leonid Shifrin Aug 17 '11 at 18:23
  • I have to correct myself: `f@(arg)`, `(arg)//f` and `f[arg]` are completely equivalent (try `MakeExpression@"f@(arg)"`), not `f@arg`, `arg//f` and `f[arg]`. I have updated my answer. Thank you for the point. – Alexey Popkov Aug 17 '11 at 19:04
  • 1
    This isn't quite correct either. Try `ff@(arg)[[1]]` vs `ff[arg][[1]]`, for example. This is correct, I suppose: `(f@arg)`, `f[arg]` and `(arg//f)` are all the same. – Leonid Shifrin Aug 17 '11 at 19:08
  • Are the precedence levels for different operators documented anywhere? – Alexey Popkov Aug 17 '11 at 19:19
  • 1
    @Alexey try `Precedence[Plus]` – acl Aug 17 '11 at 19:26
  • 1
    (should mention I saw `Precedence` mentioned in a comment to http://stackoverflow.com/q/6708591/559318 by @TomD) – acl Aug 17 '11 at 19:28
  • @acl Thank you! Evaluation of ``Grid[Reverse@Sort[{ToExpression[#, InputForm, Precedence], #} & /@ Names["System`*"]]]`` shows very interesting picture... – Alexey Popkov Aug 17 '11 at 19:48
  • @Alexey yes, I'll print that and put it on my the office's wall :) – acl Aug 17 '11 at 20:26
  • 1
    @Alexey That is a great list. (In case anyone is interested, I found out about `Precedence` in a presentation _A New Mathematica Programming Style_ by Kris Carlson available [here](http://library.wolfram.com/infocenter/Conferences/7002/)). – 681234 Aug 18 '11 at 18:53
  • @TomD Thank you for the reference! This presentation is along the lines of what Nakilon wrote in the [question](http://stackoverflow.com/q/7095557/590388). I agree with both. – Alexey Popkov Aug 19 '11 at 08:55