5
$\begingroup$

This is a question that's been bugging me for quite some time, now.

When we call the FE in a batched mode we can only get one result out. For instance, we could try to get two stylesheet files. First unbatched:

FrontEndExecute /@
 {
  FrontEnd`FindFileOnPath["Default.nb", "StyleSheetPath"],
  FrontEnd`FindFileOnPath["ReverseColor.nb", "StyleSheetPath"]
  }

{ 
 "/Applications/Mathematica.app/Contents/SystemFiles/FrontEnd/StyleSheets/Default.nb", 
 "/Applications/Mathematica.app/Contents/SystemFiles/FrontEnd/StyleSheets/ReverseColor.nb"
 }

Then we can try batching this:

FrontEndExecute@
 {
  FrontEnd`FindFileOnPath["Default.nb", "StyleSheetPath"],
  FrontEnd`FindFileOnPath["ReverseColor.nb", "StyleSheetPath"]
  }

$Failed

And... it fails. So how can I get both results out in a batched call? Can I?


What have I tried

FrontEndExecute is a just an alias for MathLink`CallFrontEnd. But it passes NoResult as the second argument.

I tried to figure out if there were other possible options for that:

hasMCall =
  Quiet@
   ToExpression[
    DeleteDuplicates@Join[Names["*`*"], Names["*`*`*"]],
    StandardForm,
    Function[Null,
     If[
      MemberQ[DownValues[#],
        MathLink`CallFrontEnd | MathLink`CallFrontEndHeld,
       ∞,
       Heads -> True
       ],
      #,
      Nothing
      ],
     HoldFirst
     ]
    ];

Cases[DownValues /@ hasMCall, 
  c : (MathLink`CallFrontEnd | MathLink`CallFrontEndHeld)[__, b_] :> b,
  ∞
  ] // DeleteDuplicates

{FE`NoResult, System`FEDump`NoResult}

But I couldn't find any in the DownValues. Beyond this, I've tried a bunch of different second arguments having to do with "Result" or whatever. They've all failed.


Batched FrontEndExecute

If you're not used to batching calls to the FE, we do it because often it's more efficient than passing commands one by one. Basically we just push all the calls in at once. As a first example, let's consider making a bunch of useless cells. We could generally write this like so:

makeBunchOfCellsUnbatched[] :=
 (
  FrontEndExecute@
   FrontEnd`NotebookSuspendScreenUpdates[EvaluationNotebook[]];
  SelectionMove[EvaluationNotebook[], After, Notebook,
   AutoScroll -> False
   ];
  Do[
   NotebookWrite[EvaluationNotebook[],
    Cell["", "MyStyle"],
    AutoScroll -> False
    ],
   200                                   
   ];
  SelectionMove[EvaluationCell[], After, Cell];
  FrontEndExecute@
   FrontEnd`NotebookResumeScreenUpdates[EvaluationNotebook[]]
  )

But we could write this with a batched call like so:

makeBunchOfCells[] :=
 FrontEndExecute@
  Flatten@{
    FrontEnd`NotebookSuspendScreenUpdates[
     FrontEnd`EvaluationNotebook[]],
    FrontEnd`SelectionMove[FrontEnd`EvaluationNotebook[], After, 
     Notebook,
     AutoScroll -> False
     ],
    Table[
     FrontEnd`NotebookWrite[FrontEnd`EvaluationNotebook[],
      Cell["", "MyStyle"],
      AutoScroll -> False
      ],
     200
     ],
    FrontEnd`SelectionMove[FrontEnd`EvaluationCell[], After, Cell],
    FrontEnd`NotebookResumeScreenUpdates[FrontEnd`EvaluationNotebook[]]
    }

And when we compare the runtimes:

makeBunchOfCellsUnbatched[] // AbsoluteTiming // First

11.5724

makeBunchOfCells[] // AbsoluteTiming // First

4.05879

We see this is much more efficient.

We can also consider a batched SetOptions:

setCellOptions[
   moveAfter : True | False : True, 
   ops : OptionsPattern[]
   ] :=
  Internal`WithLocalSettings[
   Null,
   FrontEndExecute@
    Flatten@{
      FrontEnd`NotebookSuspendScreenUpdates[
       FrontEnd`EvaluationNotebook[]],
      Map[
       FrontEnd`SetOptions[#, 
         FilterRules[{ops},
           Except[Alternatives @@ Keys@Options@Cells]]
         ] &,
       Cells[FrontEnd`EvaluationNotebook[],
        Sequence @@ FilterRules[{ops}, Options@Cells]
        ]
       ],
      If[moveAfter,
       FrontEnd`SelectionMove[FrontEnd`EvaluationCell[], After, 
        Cell],
       Nothing
       ]
      },
   FrontEndExecute@
    FrontEnd`NotebookResumeScreenUpdates[FrontEnd`EvaluationNotebook[]]
   ];

setCellOptionsUnbatched[
   moveAfter : True | False : True, 
   ops : OptionsPattern[]
   ] :=
  Internal`WithLocalSettings[
   Null,
   FrontEndExecute@
    FrontEnd`NotebookSuspendScreenUpdates[
     FrontEnd`EvaluationNotebook[]];
   FrontEndExecute /@
    Flatten@{
      Map[
       FrontEnd`SetOptions[#, 
         FilterRules[{ops}, 
          Except[Alternatives @@ Keys@Options@Cells]]] &,
       Cells[FrontEnd`EvaluationNotebook[],
        Sequence @@ FilterRules[{ops}, Options@Cells]
        ]
       ],
      If[moveAfter,
       FrontEnd`SelectionMove[FrontEnd`EvaluationCell[], After, 
        Cell],
       Nothing
       ]
      },
   FrontEndExecute@
    FrontEnd`NotebookResumeScreenUpdates[FrontEnd`EvaluationNotebook[]]
   ];

After making a bunch of cells we get results like this:

setCellOptionsUnbatched[
   Background -> RandomColor[],
   CellStyle -> "MyStyle"
   ] // RepeatedTiming // First

1.834

setCellOptions[
   Background -> RandomColor[],
   CellStyle -> "MyStyle"
   ] // RepeatedTiming // First

0.3

And so if you're making a lot of use of the FE it's generally best to Sow your calls or something and then push them all in at once. It is for this reason that I want to be able to get a batched result, too.

$\endgroup$
  • $\begingroup$ might be a naive comment, but SetAttributes[FrontEndExecute, Listable] then FrontEndExecute@ { ..., ... } works. $\endgroup$ – egwene sedai Jan 26 '18 at 22:13
  • $\begingroup$ Yes but that doesn’t batch. That’s really what I want. Otherwise I could just Map it. $\endgroup$ – b3m2a1 Jan 26 '18 at 22:24
1
$\begingroup$

It turns out that for some call types this is possible by simply sticking a FrontEnd`Value in front. CurrentValue is a case where that works and here's an example of that:

enb = EvaluationNotebook[];

MathLink`CallFrontEnd@FrontEnd`Value@
   Thread[FrontEnd`CurrentValue[enb, {FontColor, FontSize, FontWeight}]] // RepeatedTiming

{0.0004, {Automatic, 12, "Plain"}}

CurrentValue[enb, #] & /@ {FontColor, FontSize, FontWeight} // RepeatedTiming

{0.00088, {Automatic, 12, "Plain"}}

You can also set CurrentValues in batch like:

ec = EvaluationCell[];
MathLink`CallFrontEnd@FrontEnd`SetValue[
  KeyValueMap[
   FEPrivate`Set[FrontEnd`CurrentValue[ec, #], #2] &,
   <|
    FontColor -> Red,
    FontSize -> 12,
    FontWeight -> "Thin"
    |>
   ]
  ]

And revert them like:

MathLink`CallFrontEnd@FrontEnd`SetValue[
  KeyValueMap[
   FEPrivate`Set[FrontEnd`CurrentValue[ec, #], #2] &,
   <|
    FontColor -> Inherited,
    FontSize -> Inherited,
    FontWeight -> Inherited
    |>
   ]
  ]

Unfortunately this doesn't work for some tokens:

MathLink`CallFrontEnd@
 FrontEnd`Value@{
   FrontEnd`FindFileOnPath["Default.nb", "StyleSheetPath"],
   FrontEnd`FindFileOnPath["Default.nb", "StyleSheetPath"]
   }

{FrontEnd`FindFileOnPath["Default.nb", "StyleSheetPath"], FrontEnd`FindFileOnPath["Default.nb", "StyleSheetPath"]}

Still not clear how to make this work

$\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.