5
$\begingroup$

HoldForm holds an expression's parsed form not its actual "inputted form" e.g.

 SetAttributes[AttemptedRespect, HoldAll];
 AttemptedRespect[x_] := ExpressionCell[HoldForm@x, "Input"]

(assoc = <|f@x -> (x // g)|>) // AttemptedRespect

assoc = Association[f[x] -> g[x]]

where the held short forms/notation of Association/f/g are no longer respected.

To instigate respect:

 FleetingRespect[expr_] :=
 (Unprotect@Association;
 Format[Association[x__]] := <|x|>;
 Format[f[x_]] := Prefix[f@x];
 Format[g[x_]] := Postfix[x // g];
 ExpressionCell[HoldForm@expr, "Input"])

and:

(assoc = <|f@x -> (x // g)|>) // FleetingRespect

assoc = <|f@x -> (x // g)|>

which is, of course, indeed fleeting:

(assoc = Association[(x // f) -> (g@x)]) // FleetingRespect

assoc = <|f@x -> (x // g)|>

Is there a way to cajole HoldForm into more faithfully respecting what she wraps?

In other words - a function PermanentRespect such that:

(assoc = <|f@x -> (x // g)|>) // PermanentRespect

assoc = <|f@x -> (x // g)|>

and

(assoc = Association[(x // f) -> (g@x)]) // PermanentRespect

assoc = Association[(x // f) -> (g@x)]

Yoh - Respect.

Answer: The final effort in Simon Rochester's answer almost creates a pre-parsed HoldForm (without touching $PreRead) and it certainly meets the use-case requirements that originally motivated the question. Essentially I wanted to programmatically generate the "natural input shortforms" with normal output form (without using any frontend manipulations).

MakeExpression[
   RowBox@{"hf", "[", expr_, "]"} | 
   RowBox@{"hf", "@", expr_} | 
   RowBox@{expr_, "//", "hf"}, StandardForm] := HoldComplete@RawBoxes@expr;

SetAttributes[IOCells, HoldAll];

IOCells[expr_] := Grid[{
    {ExpressionCell[expr, "Input", ShowStringCharacters -> True]},         
    {ExpressionCell[ToExpression@First@expr, "Output"]}}, 
          Frame -> True,
          Alignment -> Left,
          Background -> {None, {1 -> GrayLevel[.9], 2 -> White}}];

and now both examples

Grid[{
   {hf[assoc = <|f@x -> (x // g)|>],
    hf[assoc = Association[(x // f) -> (g@x)]]}
      }] // (Map[IOCells, #, {3}] &)

enter image description here

preserve input forms, respectively.

$\endgroup$
3
  • $\begingroup$ PermanentRespect should also not have lingering side affects. e.g. FleetingRespect pollutes the name space while ensuring that Association formats with <| |> has unexpected consequences (e.g. AssociationMap[f]@<|a->1|> goes into infinite recursion ) $\endgroup$ Commented Jul 20, 2015 at 2:21
  • $\begingroup$ Because this relates to parsing, the use of PreRead, RawBoxes etc seems unavoidable although in this use case this seems disproportionate, unjustified spelunking. Functions like Inactivate/Activate perhaps suggest new attention to formatting of Input forms which made me wonder if a PermanentRespect is implementable at a higher level. $\endgroup$ Commented Jul 20, 2015 at 2:22
  • $\begingroup$ Possible duplicate: (83698). Also related (Stack Overflow): (12598697) $\endgroup$
    – Mr.Wizard
    Commented Jul 20, 2015 at 3:01

2 Answers 2

4
$\begingroup$

I think $PreRead may be your only hope (but see below). You can set it up with

$PreRead = (# /. 
  RowBox@{"PermanentRespect", "[", expr_, "]"} | 
  RowBox@{"PermanentRespect", "@", expr_} | 
  RowBox@{expr_, "//", "PermanentRespect"} :> 
    RowBox@{"RawBoxes", "[", MakeBoxes@expr, "]"}
) &;

LoseRespect[expr_] := expr /. RawBoxes -> ToExpression

Now you can do

(assoc = <|f@x -> (x // g)|>) // PermanentRespect

(assoc = <|f@x -> (x // g)|>)

or

expr = PermanentRespect[(assoc = <|f@x -> (x // g)|>)]

(assoc = <|f@x -> (x // g)|>)

The expression hasn't been evaluated:

assoc

assoc

Evaluate it with LoseRespect:

LoseRespect[expr]

<|f[x] -> g[x]|>


Despite what I said above, I think $PreRead may not be your only hope. Here's another solution that uses NotebookRead to get the box representation of the currently evaluating cell. It then looks through those boxes to find the box representation of the call to itself, and returns that wrapped in RawBoxes.

(First clear $PreRead with $PreRead =. if the definition above is still active.)

Define PermanentRespect:

SetAttributes[PermanentRespect, HoldAll]

PermanentRespect[expr_] := 
  First@Cases[
    NotebookRead[EvaluationCell[]], 
    RowBox@{"PermanentRespect", "[", boxes_, "]"} | 
      RowBox@{"PermanentRespect", "@", boxes_} | 
      RowBox@{boxes_, "//", "PermanentRespect"} /; 
      MakeExpression[boxes, StandardForm] == HoldComplete[expr] :> RawBoxes[boxes], 
    Infinity
  ]

Now PermanentRespect gives the same results as obtained above.


I'm getting more wrong by the minute about $PreRead -- here's a third solution that employs a custom definition for MakeExpression, which is used whenever boxes are converted to expressions by the front end:

ClearAll[PermanentRespect]

MakeExpression[
  RowBox@{"PermanentRespect", "[", expr_, "]"} | 
    RowBox@{"PermanentRespect", "@", expr_} | 
    RowBox@{expr_, "//", "PermanentRespect"}, 
  StandardForm
] := HoldComplete@RawBoxes@expr

This also gives the same results as above.


Also, any of the above methods can be used with .m package files, if the package file is evaluated as a notebook instead of using Get. You can define

myGet[file_] := Module[{obj},
  obj = NotebookOpen[file, Visible -> False];
  NotebookEvaluate[obj];
  NotebookClose[obj]
]

Then if the package file is loaded with myGet["package.m"], the PermanentRespect function calls inside the package will behave as above.

$\endgroup$
7
  • $\begingroup$ Yes, you might be right. I do get nervous though about tinkering with $PreRead (even if your succinct solution does seem watertight). In particular, I kind of wanted to avoid its use as it seems like bad form to potentially distribute a notebook with $PreRead altered (since things are no longer isolated as with packages). Your solution could still be useful though in the use-case of just demonstrating user-input since this could be pre-generated prior to distribution (i.e. $PreRead changed during generation but not required from the users end - with the possible drawback ... $\endgroup$ Commented Jul 21, 2015 at 2:48
  • $\begingroup$ ... that with things not generated on the fly users receive a frozen-version's output [not an unimportant consideration given how fluid the Data Science functionality seems these days]). Unfortunately even this doesn't work with $PreRead in a package set-up since while the "input form" is preserved in a .m file when assoc = <|f@x -> (x // g)|> is placed in an initialization cell, reading this back in bypasses $PreRead's setting (a condition I didn't mention in the question). $\endgroup$ Commented Jul 21, 2015 at 2:55
  • $\begingroup$ @RonaldMonson I agree that $PreRead is not ideal, for the reasons you cite. If you want to have this done in a package file, you may need to preprocess the package when loading it, somewhat along the lines of this answer. It's likely to be much messier than that case, though. $\endgroup$ Commented Jul 21, 2015 at 5:27
  • $\begingroup$ @RonaldMonson On the other hand, I think I have another notebook-based solution that doesn't require $PreRead. I'll write it up. $\endgroup$ Commented Jul 21, 2015 at 5:29
  • $\begingroup$ Great. Looks workable. I will have a look at it tonight. B.T.W note that "RawBoxes" instead of "DisplayForm" seems to mean that "LoseRespect" is not necessary? $\endgroup$ Commented Jul 21, 2015 at 8:06
2
$\begingroup$

Problem: You need to use literal (Verbatim) input lines from a notebook

After watching the one-liner competition, I decided just out of curiosity to create a function to count the number of characters of all input expression in any notebook when I stumbled into the same problem:

In[1]:= StringLength[ToString[Unevaluated[Total@Range@5]]]

Out[1]:= 15

As you can see it should be 13 because that is the StringLength of the expression Total@Range@5 when converted to a string. I tried setting the attributes of HoldAllComplete in a user defined function like so:

In[2]:= SetAttributes[charCount, HoldAllComplete]

In[3]:= charCount[Defer[expr_]]:= StringLength[ToString[Unevaluated[Total@Range@5]]]

But it did not work to force @ not to change to [ ].

Solution:

I used NotebookGet and Cases with pattern matching to format the output.

codelineLength[notebookURL_String] := 
 Cases[NotebookGet[Import[notebookURL]], 
  Cell[t_, "Input", ___] :> DisplayForm@Cell[t], Infinity] 

I added the following to make the literal input lines and their respective lengths easier to read at a glance.

Partition [
  Riffle[  StringLength[ToString[#]] & /@ 
    codelineLength["working.nb"], 
   codelineLength["working.nb"]], 2]  // TableForm 

The following one produces a small table with the required information:

TableForm[ 
 MapIndexed[f, codelineLength["working.nb"]] /. 
  f[a_, b_] :> {First@b,  StringLength@ToString@a, a} , 
 TableSpacing -> 2, 
 TableHeadings -> {None, {"line number", "length", "literal input"}}, 
 TableAlignments -> Center]

Table

I tried using codelineLength on an open notebook and it works well.

$\endgroup$

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.