35
$\begingroup$

The question is simple, but I will elaborate on the background as well for those interested in the idea:

How to define a new operator with specified precedence value?


Background

Mathematica was design to facilitate functional programming. I definitely find it easy to write code continuously: the output of a function becomes immediately the input of the next function. One thing I really miss though is a low-precedence prefix operator that applies to all things following it (up to e.g. CompoundExpression (;)). Consider the following example:

Log@N@Accumulate@# & /@ Partition[Range@300, 100] // Flatten // ListPlot

It partitions a dataset to multiple subparts, threads functions to each subpart, and then plots the joined datasets. I write such code a lot, as I find it easy that it can be written from the inside to the outside, from argument to head (right to left). The problem is that the extension of the above one might come up with intuitively does not behave like that:

ListPlot@Flatten@ Log@N@Accumulate@# & /@ Partition[Range@300, 100]

Operator precedences cause ListPlot@Flatten to be applied to each subpart of the partitioned list instead of the list as a whole. Now since I successively build up my calculations from the argument to the wrapping functions, I want to use a prefix operator. My concerns are:

  • One can use matchfix forms like f@(...) or f[...] to wrap around the Map, though it requires the matching of parentheses, which can be a PITA when multiple such functions are applied with prefix notation.
  • Using postfix // both breaks my cognitive process of writing from right-to-left, and (more importantly) breaks the principle of head-precedes-argument, which is very emphasized in Mathematica (and in $\lambda$-calculus).
  • The operator must be a free symbol. Modifying the built in @ operator should be avoided!
  • Also it must be simple enough to be used effectively. Postfix apply // is 2 keystrokes, while for example $\oplus$ requires 4 (escc+esc), making it worse than hitting end//.

So, how to define a new operator, e.g. \\ that has low precedence (perhaps 70) so that this:

ListPlot \\ Flatten \\ Log@N@Accumulate@# & /@ Partition[Range@300, 100]

equals this:

ListPlot[Flatten[Log@N@Accumulate@# & /@ Partition[Range@300, 100]]]

Frankly, I just started to wonder why this operator is not present at all in Mathematica, though WRI always emphasizes functional programming as a native style of the language.


I know I can apply Log or N to the partitioned list as a whole. I only used them here for demonstrating my case.

$\endgroup$
  • $\begingroup$ Have you studied the Notation package? I have no experience with it, but it seems to be the way to go. Another thing: converting a string to an expression starts by first parsing it. I don't think the parser is customizable, so I don't think it's possible to do this so that it'd work with the command line interface. What we might be able to do is customize how to get from boxes (not a string) to expressions and vice versa. If it is at all possible to alter the precedence (I don't know if it is or not!), the information about the precedence should be somehow embedded in the (contd.) $\endgroup$ – Szabolcs Jun 2 '12 at 10:23
  • $\begingroup$ boxes. This means that it will not be possible to enter our new operator as a simple string because we'll need to somehow enter this special box information. We might be able to define a keyboard shortcut that enters everything correctly, or maybe use the auto-replacement feature (that replaces e.g. "Mathematica" by "Mathematica") to always replace `\` by the correct box structure. Just some thoughts ... $\endgroup$ – Szabolcs Jun 2 '12 at 10:25
  • $\begingroup$ @Szabolcs: I vaguely remember encountering some file in the Mathematica folder that contains all the precedences. And also there is PrecedenceForm, though that is not really helpful here. As always, I'd rather avoid using the Notation package, as it involves a lot of overhead for such a small functionality. I really hope someone could post a simple solution for such an obvious problem, though in the meantime I try to figure out something from the Notation pkg. $\endgroup$ – István Zachar Jun 2 '12 at 10:29
  • 1
    $\begingroup$ This came up here on MG: groups.google.com/d/topic/comp.soft-sys.math.mathematica/… I seems to recall seeing it somewhere else as well, but I can't find it. While theoretically it's an interesting problem (and +1), there's always the concern that a personal custom notation that is not tied to a special custom function (but a built-in operation such as function application) would be a severe hindrance when sharing code. $\endgroup$ – Szabolcs Jun 2 '12 at 10:30
  • 1
    $\begingroup$ Yes, I am aware of this aspect (non-distributability of custom notation). As a matter of fact, I really suppressed this question since long but yesterday I felt an urge of rage-posting because I got fed up jumping back and forth of the start and end of an otherwise nicely building piece of code. Also, I hope that such changes can be done in the form of oneliners, so it would be easy to distribute the operator definition with any work intended for sharing. Lastly (a dream): WRI might decide on including it in Mathematica later if they see this is viable and useful. $\endgroup$ – István Zachar Jun 2 '12 at 10:39
32
$\begingroup$

As explained by Michael Pilat you cannot create your own compound operators* with custom precedence. (You could conceivably write your own parser as Leonid has worked on, or attempt to coerce the Box form with CellEvaluationFunction.)

You can however use an existing operator with the desired precedence. Looking at the table Colon appears to be a good choice. The operator is entered with Esc:Esc. Example:

SetAttributes[Colon, HoldAll]
Colon[f__, x_] := Composition[f][Unevaluated@x]

ListPlot \[Colon] Flatten \[Colon] Log@N@Accumulate@# & /@ Partition[Range@300, 100]

Which appears as, and produces:

Mathematica graphics

Mathematica graphics

Since raw colon is already used for Pattern this may be confusing. However, if you are willing to edit your UnicodeFontMapping.tr file you can assign any symbol you like. Here I mapped \[Colon] to Klingon A:

Mathematica graphics

This was done by changing the line starting with 0x2236 in UnicodeFontMapping.tr.


* Rojo demonstrated that one can create two-dimensional compound operators, meaning use of SubscriptBox, SuperscriptBox, OverscriptBox, etc. See:

$\endgroup$
  • $\begingroup$ Very helpful answers, both yours and Michael's, thanks! $\endgroup$ – István Zachar Jun 2 '12 at 12:37
  • $\begingroup$ obvious +IntegerPart[Pi/3] for using Klingon operators $\endgroup$ – vaxquis Mar 21 '15 at 19:40
  • 1
    $\begingroup$ @vaxquis Sadly Klingon character support was removed in version 10 and I have yet to find a way to put it back in. $\endgroup$ – Mr.Wizard Mar 22 '15 at 5:49
3
$\begingroup$

This doesn't really answer your question, but perhaps the introduction of operator forms like Map[f] in M10, as well as a new short form for Composition will satisfy you. For example, in M10+ you can do:

ListPlot @* Flatten @* Log @* N @* Map[Accumulate] @ Partition[Range @ 300, 100]

enter image description here

$\endgroup$
  • 1
    $\begingroup$ Thanks Carl! Currying operators (and Curry in v11.3) indeed helps a lot to write nice code, and this is my preferred way over introducing new symbols. $\endgroup$ – István Zachar Aug 9 '18 at 16:06
3
$\begingroup$

Update

An even simpler alternative is to use a TemplateBox so that an additional MakeExpression rules is not needed (as in my original answer):

CurrentValue[EvaluationNotebook[], {InputAutoReplacements, "\\"}] = TemplateBox[
    {},
    "Prefix",
    DisplayFunction -> ("\\\\"&),
    InterpretationFunction -> ("@"&),
    SyntaxForm -> "\[Colon]"
];

Simple example:

Hold[foo \\ x>2]
Hold[foo @ x>2]

Hold[foo[x > 2]]

Hold[foo[x] > 2]

Your more complicated example:

Hold[ListPlot \\ Flatten \\ Log@N@Accumulate@# & /@ Partition[Range@300,100]]

Hold[ListPlot[ Flatten[(Log[N[Accumulate[#1]]] &) /@ Partition[Range[300], 100]]]]

Original answer

It turns out that "\\" tokenizes, so it is possible to achieve your goals by using InputAutoReplacements and MakeExpression. First, use InputAutoReplacements to lower the precedence of "\\":

CurrentValue[EvaluationNotebook[], {InputAutoReplacements, "\\"}] = TagBox[
    "\\\\", Identity, SyntaxForm -> "\[Colon]"
];

Then, use MakeExpression to parse such boxes correctly:

MakeExpression[RowBox[{a_, TagBox["\\\\", __], b__}], StandardForm] := MakeExpression[
    RowBox[{a, "[", RowBox[{b}], "]"}],
    StandardForm
]

Your example:

Hold[ListPlot \\ Flatten \\ Log@N@Accumulate@# & /@ Partition[Range@300,100]]

Hold[ListPlot[ Flatten[(Log[N[Accumulate[#1]]] &) /@ Partition[Range[300], 100]]]]

Hold[ListPlot[Flatten[Log@N@Accumulate@# & /@ Partition[Range@300, 100]]]]

Hold[ListPlot[ Flatten[(Log[N[Accumulate[#1]]] &) /@ Partition[Range[300], 100]]]]

$\endgroup$

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.