67
$\begingroup$

V10 introduces an operator form for several functions perhaps primarily due to their role in queries as part of introducing data science functionality. At first pass it seems a lot of effort to add some syntactic sugar (given an equivalent pure functional form only ever requires an extra couple of symbols - (#, &) )? For example,Map[f,#]&[{a,b,c}]can now be shortened to Map[f][{a,b,c}], - slightly more compact but then again perhaps not such an improvement on an existing operator (short) form - f/@{a,b,c}.

So, are there some compelling examples that illustrate the rationale behind the introduction of this new construct?

Conclusion

To summarize the points made in all the informative responses:

  • In addition to avoiding the symbols ((#&)) operator forms can eliminate the need for Function in nested definitions.
  • The gains of using operator form are cumulative as they are chained together either in postfix, prefix or for some, infix form.
  • While not necessarily restricted to this area the motivation and applicability of operator forms stems from the need to provide functions as arguments in Dataset.
  • Many operator forms are built-in but when not they can be readily defined.
  • The pure and operator forms are not always semantically equivalent (natively or user-defined) with, for example, Query using their different patterns to interpret differently.
  • They can potentially be used to improve efficiency not just via code's reduced leaf-count but in reduced algorithmic complexity.
  • They are potentially a rich source of language improvement from mimicking natural language patterns, code refactoring, debugging or automated and non-deterministic parsing via corpus-derived context.

Update V 10.3.1 ( 01/01/16 )

A new answer gives an overview of the idioms used for system operator forms and how these can be intermingled with user-defined operator forms.

$\endgroup$
11
  • 1
    $\begingroup$ not Map[f]{a,b,c} but Map[f][{a,b,c}]. $\endgroup$
    – Wouter
    Commented Aug 3, 2014 at 11:51
  • $\begingroup$ Corrected. Thanks. $\endgroup$ Commented Aug 3, 2014 at 13:20
  • 2
    $\begingroup$ I find them indispensable when exploring any kind of data, since you can just keep tacking things like // Map[..#..&] or //Select[..#..&] onto the end of expressions. $\endgroup$
    – jtbandes
    Commented Aug 3, 2014 at 18:21
  • 2
    $\begingroup$ I am so glad to see people coming to the same conclusions that I did about operator forms! $\endgroup$ Commented Aug 7, 2014 at 18:11
  • 1
    $\begingroup$ @TaliesinBeynon, why not operator form for everything --> Mod[11], ListPlot[Joined->True], Translate[{0,1}], StringSplit[{"..."}] $\endgroup$ Commented Sep 7, 2014 at 18:29

6 Answers 6

38
$\begingroup$

I would have liked to have more experience with the operator forms before this question was asked as I am short on examples, and I'm sure my opinion will evolve over time. Nevertheless I think I have enough familiarity with similar syntax to provide some useful comments.

Taliesin Beynon provided some background for this functionality in Chat:

Operator forms have turned out to be a huge win for writing readable code. Unfortunately I can't remember whether it was Stephen or me who first suggested them, so I don't know who should get the credit :). Either way it was a major (and risky) decision, and I had to argue with a lot of people in the company who remained skeptical, so credit goes to Stephen for just pushing it through. But they were motivated by the needs of Dataset's query language, which is an interesting historical detail I think.

We see that m_goldberg is correct in seeing operator forms as being important to Dataset.

Taliesin also claims that operator forms are "a huge win" for readability. I agree with this and have been a proponent of SubValues definitions, which is basically what "operator forms" are. I also like Currying(1),(2) though I haven't embraced it to the same degree.

You comment that operator forms only save a few characters over anonymous functions and this is usually true, but these characters, and more importantly the semantics behind them, are nevertheless significant. Being able to treat functions with partially specified parameters as functions (Currying) frees us from the cruft or baggage of a lot of Slot and Function use. Surely these are easier to read and write:

fn[1] /@ list                   (*  fn[1, #] & /@ list             *)

SortBy[list, Extract @ 2]       (*  SortBy[list, Extract[#, 2] &]  *)

Note that I did not choose to use the operator form of SortBy here.

Since Mathematica uses a generally functional language these kinds of operations are frequent, which mean that these effects quickly compound. Code that contains multiple Slot Functions can be quite hard to read as it is not always clear which # belongs to which &. As a hurriedly contrived example consider this snippet:

(SortBy[#, Mod[#, 5] &] &) /@ (Append[#, 11] &) /@ Partition[Range@9, 3]

If we first provide "operators forms" for functions that do not presently have them:

partition[n_][x_] := Partition[x, n]
mod[n_][m_] := Mod[m, n]

Then write the line above using such forms in all applicable places:

SortBy[mod @ 5] /@ Append[11] /@ partition[3] @ Range @ 9

This is a considerable streamlining of syntax and much easier to read.

The example above is also semantically simpler:

Unevaluated[(SortBy[#1, Mod[#1, 5] &] &) /@ (Append[#1, 11] &) /@ 
    Partition[Range[9], 3]] // LeafCount

Unevaluated[SortBy[mod @ 5] /@ Append[11] /@ partition[3] @ Range @ 9] // LeafCount
20

11

Theoretically that could pay dividends in performance though I am uncertain of the present reality of this. Some operations are slower, possibly due to an inability to compile, while others are faster. However I believe that this simplification opens the door for future optimizations.

$\endgroup$
3
  • 2
    $\begingroup$ Two useful takeaways here: Firstly, the extra brevity from avoiding (#&) is only part of the story. Operator forms also remove the need for Function usage for nested pure functions but further, even this doesn't seem to capture the advantages since when it comes to readability and conceptualisation it really does seem to be a case of the "whole being greater than the sum of the parts". Secondly, even when an operator form is not built in, one can readily be defined if warranted by the code. $\endgroup$ Commented Aug 4, 2014 at 13:22
  • $\begingroup$ This last point -defining your own operator form if needs be - is touched upon in the documentation (e.g. via the matches definition in the "titanic neat example") although perhaps it would have been worth emphasising a little more given its role in building up more powerful queries. It also relates to my speculation about these forms being automatically detectable given that most functions have a natural "data argument" (and/or other arguments can be inferred from the context). It also raises the spectre of wholesale code refactoring for improving the maintainability of legacy codebases. $\endgroup$ Commented Aug 4, 2014 at 13:37
  • $\begingroup$ @Mr.Wizard , I think your last example is even simpler to understand written this way because operations are written in the same order they are processed: Range@9//partition[3]//Map[Append[11]]//Map[SortBy[mod@5]] . I use the style described in this post mathematica-guide.blogspot.co.uk/2011/12/… $\endgroup$
    – faysou
    Commented Apr 28, 2015 at 14:47
46
$\begingroup$

For me the operator forms of Map and Apply will probably provide the most important benefits in terms of code readability. Often I need to apply a sequence of transformations to some data, and I am fond of infix notation for this purpose. For example I find

a ~Position~ 0 ~SortBy~ Last

more readable than the "conventional"

SortBy[Position[a, 0], Last]

because I do not have to scan backwards and forwards in the expression to match the SortBy with the Last.

This is only possible when using functions which take the data as their first argument. Because Map and Apply take the data as their second argument, they do not fit easily into the left-to-right infix syntax. If my final step is to map Max across the list I would need to use something like

a ~Position~ 0 ~SortBy~ Last ~(#2 /@ #1 &)~ Max

(if I was determined to stick with infix), or more likely

a ~Position~ 0 ~SortBy~ Last // Max /@ # &

In both cases I am having to use a pure function just to get the arguments of Map in the correct order. In practice I would probably abandon the left-to-right principle and put the last operation at the beginning of the expression:

Max /@ (a ~Position~ 0 ~SortBy~ Last )

The operator form means that I can chain the transformations in a very natural way:

a // Position[0] // SortBy[Last] // Map[Max]
$\endgroup$
14
  • 1
    $\begingroup$ +1 This is the first answer to this question I understand by just reading it :) $\endgroup$
    – eldo
    Commented Aug 3, 2014 at 21:37
  • $\begingroup$ Although I think you know I fully agree with you regarding "I am fond of infix notation ... more readable than the 'conventional'" it seems most people disagree. I'm really not sure why. Simple unfamiliarity? $\endgroup$
    – Mr.Wizard
    Commented Aug 4, 2014 at 0:03
  • 1
    $\begingroup$ Er… is the /* in the last example irreplaceable? // seems to have the same effect. $\endgroup$
    – xzczd
    Commented Aug 4, 2014 at 4:40
  • 1
    $\begingroup$ @xzczd, yes you could use // the way I have written it. Perhaps that would be clearer actually. $\endgroup$ Commented Aug 4, 2014 at 8:40
  • 1
    $\begingroup$ I agree that a // Position[0] // SortBy[Last] // Map[Max] seems (the most) natural. Perhaps even more so than Map[Max] @* SortBy[Last] @* Position[0] @* a due to the spacing and a natural proclivity to read left-to-right. I suspect that increased postfix usage maybe end up being one of the main effects of operator forms on code bases. $\endgroup$ Commented Aug 4, 2014 at 13:01
24
$\begingroup$

I find the value of the new operator forms becomes critical when working with datasets. Consider

titanic = ExampleData[{"Dataset", "Titanic"}];
titanic[Count[#], "survived"] & /@ {True, False, _Missing}
{500, 809, 0}

Derive a data set for analyzing the survival of very young passengers.

cutoff = 8;
youngest = titanic[All, {"age", "survived"}][Select[#age <= cutoff &]];
pts = 
  Table[
    Function[{x, y}, youngest[Select[#age == x && #survived == y &] /* Length]][x, y], 
    {x, Range @ cutoff}, {y, {True, False}}] // Transpose;
ListPlot[Tooltip /@ pts,
  PlotStyle -> {Black, Red},
  PlotMarkers -> {Automatic, 14},
  PlotLegends -> {"Survived", "Perished"},
  AxesLabel -> {"Age", "Count"}]

survival

Without the new operator forms for functions like Count and Select, working with datasets would much more awkward. It is only speculation on my part, but I believe datasets (i.e., structured data) provided the motivation for implementing the new forms.

$\endgroup$
1
  • $\begingroup$ In a previous comment it was mentioned how operator forms can be used to refactor code as part of improving readability but also efficiency. As illustrated by other answers, operator forms can replace occurrences of Function (introduced to deal with nested #&'s) suggesting a refactoring in the code of this answer and in particular a dropping of one of the Table's iterators. The snippet Table[youngest[Select[#age == x &] /* CountsBy[#survived &] /* ({#[True], #[False]} &)]//Normal,{x,Range@cutoff}] is equivalent, perhaps of comparable readability but certainly improved efficiency-wise. $\endgroup$ Commented Aug 7, 2014 at 2:21
18
$\begingroup$

Intermingling Operator Forms & Linguistic Connections

This answer attempts to draw out linguistic connections in understanding why operator forms seem so useful, a process that can perhaps point to further utility. Operator Forms are a type of modularization with the standard re-use and combinatory advantages but IMO the biggest benefit is cognitive - it allows you to focus on what is relevant in defining/applying an operation. Natural language does this pervasively effectively modularizing by repeatedly sampling contexts.

Note this post was generated (and adapted) from a notebook as part of road-testing a developing markdown - hence its formatting may be less than optimal. It's also long.

V10.3.1 provides functionality for viewing all built-in operator forms (via so-called Curryable symbols) weighted here according to SE usage (bearing in mind that most of this predates operator form's introduction). [See also other listings].


curryable = 
 WolframLanguageData[
    EntityClass["WolframLanguageSymbol", "Curryable"]] // 
   Join[#, WolframLanguageData /@ {CellularAutomaton, TuringMachine, 
       SubstitutionSystem}] & // Sort


  ImageCollage[{Rasterize[#, "Image", RasterSize -> 400, 
     ImageSize -> {500, Automatic}], 
    "StackExchange" /. 
     EntityValue[#, 
      EntityProperty["WolframLanguageSymbol", "Frequencies"]]} & /@ 
  curryable]

enter image description here

Without weighting all operator forms can be listed.


ImageCollage[(Rasterize[#, "Image"] & /@ curryable), Automatic, 500, 
 Method -> "ClosestPacking"]

enter image description here

We can compare non-operator forms with their corresponding canonical form to observe a prevailing systemization and in particular, the types of arguments being picked off.


findFirstLast[
   ent_] := (StringCases[
      ent["Name"] ~~ 
        "[" ~~ (st__ /; ! StringEndsQ["Association"]@st) ~~ "]" // 
       Shortest]@(ent[
       EntityProperty["WolframLanguageSymbol", "PlaintextUsage"]]) // 
    Query[{First, Last}]);

findFirstLast[
   Entity["WolframLanguageSymbol", 
    "ReplaceAll"]] := {"ReplaceAll[expr,rules]", "ReplaceAll[rules]"};

FirstLastUsageExpressions = 
  ToExpression[#, InputForm, HoldForm] & /@ findFirstLast /@ curryable;

makeGrid[pair_] := 
  Grid[{ExpressionCell[#, "Input", 
       FontFamily -> "American Typewriter"]} & /@ pair, 
   Alignment -> Right];

ImageCollage[(Rasterize[#, "Image", RasterSize -> 700] & /@ 
   makeGrid /@ FirstLastUsageExpressions), "Fit", 
 ImagePadding -> 4, Padding -> Orange, 
 Method -> "Columns"]

enter image description here

This collage illustrates a clear idiom at play; the argument being dropped from the original function invariably refers to a "fixed" structure that is subsequently operated on by the corresponding operator form. The fixed structures identified as future operands are collected in the collage below left whereas the below right collage collects all the remaining argument(s) used to define the operator form (as per the code that follows).

enter image description here

(* code for the collage/dataset immediately above *)

fixedCount = 
  FirstLastUsageExpressions[[All, 1, All, 
       1]] // DeleteCases[HoldForm@f] // Counts // 
    Sort // Reverse;
(* Remove f as in MapAt the first argument is not the fixed on \
jettisoned for the corresponding operator form *)

operatorArgs = 
  Replace[_[args__] -> Invisible[""][args]] @@@ 
       FirstLastUsageExpressions[[All, 2, 
        All]] // Rest // Counts // Sort // Reverse;

rasterizeKeys = 
  KeyMap[Rasterize[
     ExpressionCell[#, "Input", FontFamily -> "American Typewriter"], 
     "Image", RasterSize -> 200] &];
splitDataset[splitAt_] := 
  Function[d, 
   Row[{d[[;; splitAt]], 
     d[[splitAt + 1 ;; 
       Length@d]]}]];

Grid[{
  {GraphicsGrid[{
     {ImageCollage[rasterizeKeys@fixedCount],
      ImageCollage[rasterizeKeys@operatorArgs]}
     }, ImageSize -> {900, Automatic}, Spacings -> {90, 0}]},
  {
   Grid[{{Spacer@90, Dataset[HoldForm /@ fixedCount], Spacer@120, 
      Dataset[HoldForm /@ operatorArgs] // splitDataset@12}}, 
    Alignment -> {Center, Top}]}
  }, Alignment -> {Center, Top}
 ]

The value of elucidating this dilineation is two-fold: 1) It highlights a network effect enhancable not only through system operator forms but also through an intermingling with user-defined operator forms 2) It points to Operator Form's connection to the central notion of Mutability/Immutability

Network Effect & User-defined Operator Forms

A network effect emerges from being able to combine operator forms both built-in and user-defined. It is straightforward to create user-defined operator forms although several SE questions suggest a possible ongoing under-utilization. Given how much it can improve a code-base's readabiity, a template is perhaps worth making explicit for new users:

Given a function head with the fx argument corresponding to a nominally "fixed" structure


head_[argsH___, fx_, argsT___] := code 

                                 (* code includes argsH, fx, argsT *)

An operator form ohead can be defined as follows:


ohead_[argsH___, argsT___] := Function[fx, code]

                                           (* code includes argsH, fx, argsT *)

with subsequent invocations taking the form:


head[argsH, argsT]@fx

Note:

  • A more direct implementation involving subvalues head[_argsH___,argsT__][fx_]:= code although there are certain advantages for the more general idiom shown.

  • fx, is typically the first argument called (i.e. argsH typically matches the empty sequence) in system functions with the notable exceptions of MapAt, Insertand Entity.

  • The operator form typically consists of a single argument (with exceptions at the same functions just mentioned).

  • Here the operator form ohead is distinguished from the canonical form head to ensure their disambiguation although built-in operator forms avoid this by limiting their functional scope.

For consistency I have found it worthwhile sticking to these conventions in creating my own user-defined operator forms although having said this, sometimes it can be useful to loosen such restrictions and to what extent this can/should be done is to be canvassed.

Immutability/Mutability

The importance of the Wolfram Language's immutability in rapid development and experimentation has been well-documented and in this context manifests through operator forms' ability to create new structures each time they are applied - thereby leaving untouched underlying definitions. When such underlying definitions do need to be changed/augmentation (say following a period of experimentation/prototyping) then all this operator form functionality can again be harnessed this time by an operator-form of Set(most naturally via an infix operator akin to @=, as previously advocated).

Finally, note some anomolies/variations in the 10.3.1 version: The operator forms for CellularAutomation,TuringMachine, and SubstitutionSystem assume default parameters for their last arguments (the time step) while the operator form of AssociationThread and DataBinAdd are yet to appear in the Documentation Centre [also DataBinAdd's implementation violates the just-described idiom]

Different Targets

The consistency involved in omitting a "fixed structure" in the operator form adds greatly to the idiom's pedagogy (which IMO makes the implementation of DatabinAdd a mistake [unless I've missed a compelling use-case justifying such a violation]) but even "second best" operator forms can still be useful. Take MemberQ as an example:

The systemic version translates the linguistic request:

enter image description here


Select[{{a, b, c}, a, z, {x, y, z}}, MemberQ@a]

(*  {{a, b, c}}  *)

Alternatively if our "fixed" structure is a potential list element we might instead request

enter image description here


oMemberQ[ls_List] := Function[elem, MemberQ[ls, elem]];

Select[{{a, b, c}, a, z, {x, y, z}}, oMemberQ@{a, b, c, x, y, z}]

 (* {a, z} *)

with perhaps a more natural application being a function

enter image description here


f[x_?(oMemberQ@{a, b, c, x, y, z})] := expr;

{f[a], f[{a, b, c}]}

(* {expr, f[{a, b, c}]}  *)

Multi-argument Operator Forms

Note that almost all system-defined operator forms contain a single argument and in the rare cases where two-arguments are used (MapAt, Insert, Entity) their corresponding canonical form always contain three or more arguments. Hence, to avoid ambiguity, the number of arguments in an operator form apparently always needs to be less than the canonical usage. As a consequence, operators forms become restricted to this canonical usage (currying is a slight misnomer here as it is more accurately an example of partial function application although admittedly partialfunctioning doesn't quite have the same ring) .

Take for example, Lookup which takes a handy third argument to be returned if the Lookup fails. The natural operator form Lookup[key, default] is avoided since the two-argument version has been already been reserved for the canonical Lookup[assoc, key]; nonetheless we can define a handy helper that

enter image description here


oLookup[key_, def_] := Function[assoc, Lookup[assoc, key, def]];

oLookup["a", "def"]@<|"b" -> c|>

(* "def" *)

Composing Operator Forms

This is one area especially ripe for development given its amenablity to network effects.

Suppose we wanted to perform a previous search but this time wholly with operator forms

enter image description here


Select[MemberQ@a]@{{a, b, c}, a, z, {x, y, z}}

(* {{a, b, c}} *)

This form is only possible as of V10.3 since both Select and MemberQ now possess operator forms whose composition can now therefore lead to a new operator form. There are however, more general cases where we might want to define new operator forms from functions that currently do not, or seem unlikely to have, operator forms of their own (at least under current design idioms).

Suppose, for example, we wanted to only

enter image description here

we might be tempted to try


Select[!MemberQ@a]@{{a, b, c}, a, z, {x, y, z}}

(* {} *)

but this fails because feeding an operator form to Not doesn't yield another operator form as it's not geared for this construction. Rectifying:


\[Dalet][op_] := Function[oper, Not[op@oper]];

Select[\[Dalet]@MemberQ@a]@{{a, b, c}, a, z, {x, y, z}}

 (* {a, z, {x, y, z}} *)

Clearly FreeQ could have been used here but sometimes one wants a particular mode of expression while Boolean operator forms frquently arise where built-in alternatives don't exist.

For example, It feels like we should be able to translate

enter image description here


Select[MemberQ@c || MemberQ@z]@{{a, b, c}, a, z, {x, y, z}}

(* {} *)

but without || accepting operator forms we are instead forced back to pure functions.


Select[((MemberQ@c)@# ) || ((MemberQ@z)@#) &]@{{a, b, c}, a, z, {x, y, z}}

(* {{a, b, c}, {x, y, z}} *)

Rectifying by co-opting [vee] we have:


op1_\[Vee]op2_ := Function[arg, Or[op1@arg, op2@arg]];

Select[MemberQ@c\[Vee]MemberQ@z]@{{a, b, c}, a, z, {x, y, z}}

(* {{a, b, c}, {x, y, z}} *)

I find these so useful that I have all Boolean variants re-defined but this idiom's utility is not restricted to logic operations. Suppose we wanted to

enter image description here


SortBy[-Length]@{{a, b, c}, {x, y, z}}

This feels like something we ought to be able to do without pure functions; likewise for arbitrary-depth queries

enter image description here


Select[Length > 2 && EvenQ@Length]@{{a, b, c}, {x, y, z}}

Hence a more general solution would entail operator forms automatically "ascending" to the nearest relevant operator form. Not straightforward but extremely useful given multiplying benefits from network effects.

Disambiguation

As the number of these operator function's grow so to does the challenge of disambiguation; There are three strategies:

  1. Named Derivatives
  2. Short-form Notation
  3. Heuristics

(1) is used for simplicity in the above examples but is not so clean (n.b. also Sort and SortBy), (2) is useful for the most common operator forms (as per the first collage, it is no accident the most fundamental/popular functions Map, Apply and ReplaceAll all had their own (short-form) "operator forms" long before V10 arrived) whereas the constant naming in (3) seems the only sustainable solution. Clearly such heuristics can advantageously consist of typing and pattern-matching but for mine the most interesting and promising avenue involves incorporating context to the same extent as routinely occurs with natural language interpretation.

The natural language in Wolfram Alpha still seems too imprecise to be reliably intermingled with Wolfram Language while the latter's precision tends to mitigate against opening spaces for flexibility and fruitful ambiguity. Operator forms perhaps offer a case study/benchmark for how these might be effectively coalesced.

$\endgroup$
3
  • 1
    $\begingroup$ Re "Select if not a member", try: Select[MemberQ[a] /* Not]@{{a, b, c}, a, z, {x, y, z}} $\endgroup$ Commented Jan 1, 2016 at 19:19
  • 1
    $\begingroup$ @alancalvitti yes or even Select[Not@*MemberQ@a]@{{a, b, c}, a, z, {x, y, z}} to preserve the linguistic order but would be nice to have something similar for "compositions" where the operand is at greater depth. $\endgroup$ Commented Jan 2, 2016 at 3:57
  • $\begingroup$ I think the first Blank belongs to head in: head[_argsH___,argsT__][fx_]:= code $\endgroup$ Commented Dec 8, 2016 at 19:02
14
$\begingroup$

Very nice answers. I wanted to add something else.

One typical "Mathematica way" of coding involves overloading a function with several definitions, that do different things according to what arguments are passed (I actually abuse this). You can pattern match by head with things like f[x_Integer]:=... and f[x_Real]:=....

I see the Dataset/Query functionality not so much as a reason why the operator forms are useful, but as an example of how they can be useful. Dataset/Query take functions as arguments. Functions aren't very easy to "pattern match" according to what they are for; but with operators, this becomes easy. Dataset/Query behave differently for a _Select argument (descending, filtering function) than a general _Function (ascending).

Now you can design your functions to take functions as arguments, and behave differently if those are functions that apply, functions that map, functions that filter, and do it in a way that's neatly integrated with built-ins

$\endgroup$
6
  • $\begingroup$ I think some examples will help me understand your point better. $\endgroup$
    – RunnyKine
    Commented Aug 4, 2014 at 4:34
  • 1
    $\begingroup$ @RunnyKine These two are different Query[Select[EvenQ], 1]@{{2}} and Query[Select[#, EvenQ] &, 1]@{{2}}. The first one is recognized as descending and treated specially $\endgroup$
    – Rojo
    Commented Aug 4, 2014 at 4:40
  • $\begingroup$ @RunnyKine, so, Query is overloaded for "filtering" arguments, and it recognizes them through their heads, in a neat way, thanks to the operator forms. _MaximalBy, _Select, _KeySelect, etc $\endgroup$
    – Rojo
    Commented Aug 4, 2014 at 4:41
  • $\begingroup$ @RunnyKine I don't know to what extent doing these kinds of things will become a nice habit of mine, its too soon. People like Leonid or WReach that are more experienced programmers might have a better idea at this point about how important this is. $\endgroup$
    – Rojo
    Commented Aug 4, 2014 at 4:42
  • 1
    $\begingroup$ Ah, I see. Thanks for explaining. +1 $\endgroup$
    – RunnyKine
    Commented Aug 4, 2014 at 4:47
10
$\begingroup$

Here's a stab at a second pass: Syntactic sugar shouldn't be underestimated given its cumulative effects (also only a limited number of functions can have shortforms and sometimes for precedence reasons four symbols are needed in the pure form - (#)&)

An example: Suppose it is desired to take keys/values "f" through to "h" and "p" through to "r" in assoc3, an association that associates the nth letter of the alphabet with the number n.

assoc3 = AssociationThread[CharacterRange["a", "z"] -> Range@26];

One solution

Merge[Query[Apply[Span,Partition[Flatten[Map[FirstPosition[Keys@assoc3,#]&,{"f","h","p","r"}]],2],{1}]][assoc3],First]

(* ->    <|"f" -> 6, "g" -> 7, "h" -> 8, "p" -> 16, "q" -> 17, "r" -> 18|> *)

has a natural object being "operated on", {"f","h","p","r"} being pushed deeper into the code and possibly not as clear as using operator forms:

Merge[First]@*(Query[#][assoc3] &)@*(Span@@@#&)@*(Partition[#, 2]&)@*Flatten@((FirstPosition[Keys@assoc3,#]&)/@{"f","h","p","r"})

 (* ->   <|"f" -> 6, "g" -> 7, "h" -> 8, "p" -> 16, "q" -> 17, "r" -> 18|> *)

or something where operator forms are potentially extended to all functions and to include shortform versions

Merge[First]@*Query[][assoc3]@*Span@@@ @*Partition[2]@*Flatten@*FirstPosition[Keys@assoc3]/@{"f","w","s","w"}

 (* ->    <|"f" -> 6, "g" -> 7, "h" -> 8, "p" -> 16, "q" -> 17, "r" -> 18|> *)

(* not actual input/output speculative only *)

At a more abstract level operator forms seem to be a way for harnessing context in that omitted arguments are being supplied by the object being operated on. There are however some functions that surprisingly don't seem to have operator forms:

{Query[Sort[Greater]][{1, 2, 3}], Query[Sort[#, Greater] &][{1, 2, 3}]}
(* ->   {Sort[Greater][{1, 2, 3}], {3, 2, 1}} *)

or that don't operate in certain situations

{Query["Min" -> Min][{1, 2, 3}], Query["Min" -> Min@# &][{1, 2, 3}]}
(* -> {{1, 2, 3}, "Min" -> 1} *)

suggesting the advantages of automating the process of detecting operator forms since this seems closer to the way humans linguistically operate

Consider an operator form of the pangram:

"Illustrate the quick brown fox jumping over the lazy dog" 

Illustrate @* Fox$_{(the, quick,brown)}$ @* JumpingOver @* Dog$_{(the, lazy)}$

or a variation involving more "Center Embedding" a linguistic analog of the progressive enveloping of {"f","h","p","r"}

"Illustrate the fox, the cat the dog the flea bit crippled fought jumping over the lazy dog."

which seems difficult to parse possibly due to limitations in human's short-term memory as we long for a bottoming out to attach subjects to their matching predicates. Alternatively

Illustrate @* Fox$_{(the, quick,\ brown,\ fought\ by\ cat_{crippled \ by\ dog_{bitten\ by\ flea}})}$ @* JumpingOver @* Dog$_{(the, lazy)}$

seems more parseable. These involve more descriptive forms than operational (but the same principle applies) but more operational forms also come in variable order linguistically. Consider

"Illustrate the quick brown fox jumping over the lazy dog and exhibit by first performing gilded framing and then sending to the Louvre by rail."

which when put together in "operator form" expresses:

(Illustrate @* Fox$_{(the, quick,\ brown,\ fought\ by\ cat_{crippled \ by\ dog_{bitten\ by\ flea}})}$ @* JumpingOver @* Dog$_{(the, lazy)}$) // Frame$_{guilded}$ // Send$_{rail}$ // Exhibit$_{Louvre}$

Naturally this could all be enhanced by flexible code-folding (and say tool-tip illustration of the structural change performed by the operator) but the point is it becomes more natural (possible?) with operator form positioning. It also indicates a possible bridging between the linguistic but (destined?) vagueness of wolfram-alpha queries and the precision but non-linguistic form of the Wolfram Language (at least initially in restricted domains)

$\endgroup$
0

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.