Function Repository Resource:

ReadableForm

Source Notebook

Display an expression in a format intended to maximize readability

Contributed by: Richard Hennigan (Wolfram Research)

ResourceFunction["ReadableForm"][expr]

displays a version of expr similar to InputForm[expr] that is formatted to maximize readability.

Details and Options

ResourceFunction["ReadableForm"] has the following options:
CachePersistenceAutomaticspecifies how internal caching should be handled
CharacterEncoding"Unicode"which character encoding to use
"DynamicAlignment"Falsewhether to use context-sensitive alignment across lines
"FormatHeads"Automatica set of heads {h1,h2,} such that hi[] should be formatted in StandardForm
"IndentSize"4how many spaces to use for indenting
"InitialIndent"0how much additional indentation to apply to each line
PageWidth80the target character count for each line
PerformanceGoal"Quality"aspects of performance to try to optimize
"PrefixForm"Truewhether to use prefix form (f@x) when appropriate
"RealAccuracy"Automaticnumber of digits to the right of the decimal point to display for real numbers
"RelativeWidth"Falsewhether to count indentation in page width
Possible settings for CachePersistence include:
Noneimproves memory usage at the cost of speed
Fullimproves speed at the cost of memory usage
Automaticuses a balanced mix
The "DynamicAlignment" option is considered experimental and is currently only applied for a small set of expression types.
Specifying "InitialIndent"n will add an additional n×s spaces to the beginning of each line, where s is the value of "IndentSize".
The value for PageWidth specifies a desired target width and is not a hard limit.
The value for "RealAccuracy" can be one of the following:
Automaticreal numbers are displayed using normal InputForm behavior
na nonnegative integer specifying a maximum number of digits to display after decimal points
With the setting "RealAccuracy"n, real numbers will always display with at least one digit to the right of the decimal point. If there are no digits available, a zero will be used.
With the setting "RelativeWidth"True, the leading whitespace is not counted when determining the width of a line.
SaveReadableNotebook should be used instead of ResourceFunction["ReadableForm"] if formatting notebook files.

Examples

Basic Examples (2) 

View the code underlying a formatted expression:

In[1]:=
\!\(\*
GraphicsBox[
{RGBColor[0, 1, 0], Thickness[Large], RectangleBox[{0, -1}, {2, 1}], 
{RGBColor[1, 0, 0], DiskBox[{0, 0}]}, 
{RGBColor[0, 0, 1], CircleBox[{2, 0}]}, 
{RGBColor[1, 1, 0], PolygonBox[{{2, 0}, {4, 1}, {4, -1}}]}, 
{RGBColor[0.5, 0, 0.5], Arrowheads[Large], ArrowBox[
       NCache[{{4, Rational[3, 2]}, {0, Rational[3, 2]}, {0, 0}}, {{4, 1.5}, {0, 1.5}, {0, 0}}]], 
{GrayLevel[0], Dashing[{Small, Small}], LineBox[{{-1, 0}, {4, 0}}]}}}]\) // ResourceFunction[
 "ReadableForm"]
Out[1]=

Visually inspect properties of formatted objects:

In[2]:=
ResourceObject[<|"Name" -> "LeNet Trained on MNIST Data", "UUID" -> "050b1a0a-f43a-4c28-b7e0-72607a918467", "ResourceType" -> "NeuralNet", "Version" -> "1.16.0", "Description" -> "Identify the handwritten digit in an image", "RepositoryLocation" -> URL[
    "https://www.wolframcloud.com/objects/resourcesystem/api/1.0"], "WolframLanguageVersionRequired" -> "11.1", "ContentElements" -> {"ConstructionNotebook", "ConstructionNotebookExpression", "EvaluationNet", "UninitializedEvaluationNet", "EvaluationExample"}|>, ResourceSystemBase -> "https://www.wolframcloud.com/objects/resourcesystem/api/1.0"] // ResourceFunction["ReadableForm"]
Out[2]=

Scope (2) 

Input-style syntax highlighting is used:

In[3]:=
ResourceFunction["ReadableForm"][Hold[f[x_] := Table[i + 1, {i, x}]]]
Out[3]=

Use Unevaluated to prevent the expression from evaluating:

In[4]:=
ResourceFunction["ReadableForm"][Unevaluated[Echo[\!\(
\*SubsuperscriptBox[\(\[Integral]\), \(0\), \(1\)]\(
\*FractionBox[\(Log[
\*FractionBox[\(1\), \(2\)]\ \((1 + 
\*SqrtBox[\(1 + 4\ x\)])\)]\), \(x\)] \[DifferentialD]x\)\)]]]
Out[4]=

Generalizations and Extensions (2) 

ToString will preserve the whitespace:

In[5]:=
if = ResourceFunction["ReadableForm"][
TestResultObject[<|"TestClass" -> None, "TestIndex" -> 1, "TestID" -> None, "Outcome" -> "MessagesFailure", "Input" -> HoldForm[1/0], "ExpectedOutput" -> HoldForm[ComplexInfinity], "ActualOutput" -> HoldForm[ComplexInfinity], "ExpectedMessages" -> {},
     "ActualMessages" -> {
HoldForm[
Message[
MessageName[Power, "infy"], 
HoldForm[0^(-1)]]]}, "AbsoluteTimeUsed" -> Quantity[0., "Seconds"], "CPUTimeUsed" -> Quantity[0., "Seconds"], "MemoryUsed" -> Quantity[1136, "Bytes"]|>]]
Out[5]=
In[6]:=
ToString[if]
Out[6]=

CopyToClipboard will also preserve much of the formatting:

In[7]:=
CopyToClipboard[if];
Paste[]

Options (21) 

CachePersistence (3) 

With CachePersistenceNone, ReadableForm will not use any caching, which reduces overall memory usage but is the slowest method:

In[8]:=
First[RepeatedTiming[
  ToString[ResourceFunction["ReadableForm"][Hold[Condition[
PositionLargest[
Pattern[RH`ReadableForm`list, 
Blank[List]]], 
AllTrue[RH`ReadableForm`list, NumericQ]] := First[
FirstPosition[RH`ReadableForm`list, 
Max[RH`ReadableForm`list]]]; Condition[
PositionLargest[
Pattern[RH`ReadableForm`list, 
Blank[List]], 
Pattern[RH`ReadableForm`n, 
Alternatives[
Blank[Integer], 
HoldPattern[UpTo][
Blank[Integer]]]]], 
AllTrue[RH`ReadableForm`list, NumericQ]] := Take[
Flatten[
Map[Position[RH`ReadableForm`list, #]& , 
DeleteDuplicates[
TakeLargest[RH`ReadableForm`list, RH`ReadableForm`n]]]], RH`ReadableForm`n]], CachePersistence -> None]]]]
Out[8]=

With the default behavior of CachePersistenceAutomatic, ReadableForm will remember some values between evaluations to gain a modest performance boost:

In[9]:=
First[RepeatedTiming[
  ToString[ResourceFunction["ReadableForm"][Hold[Condition[
PositionLargest[
Pattern[RH`ReadableForm`list, 
Blank[List]]], 
AllTrue[RH`ReadableForm`list, NumericQ]] := First[
FirstPosition[RH`ReadableForm`list, 
Max[RH`ReadableForm`list]]]; Condition[
PositionLargest[
Pattern[RH`ReadableForm`list, 
Blank[List]], 
Pattern[RH`ReadableForm`n, 
Alternatives[
Blank[Integer], 
HoldPattern[UpTo][
Blank[Integer]]]]], 
AllTrue[RH`ReadableForm`list, NumericQ]] := Take[
Flatten[
Map[Position[RH`ReadableForm`list, #]& , 
DeleteDuplicates[
TakeLargest[RH`ReadableForm`list, RH`ReadableForm`n]]]], RH`ReadableForm`n]], CachePersistence -> Automatic]]]]
Out[9]=

Using CachePersistenceFull will give the fastest performance, but the extra memory used is not released for the remainder of the kernel session:

In[10]:=
First[RepeatedTiming[
  ToString[ResourceFunction["ReadableForm"][Hold[Condition[
PositionLargest[
Pattern[RH`ReadableForm`list, 
Blank[List]]], 
AllTrue[RH`ReadableForm`list, NumericQ]] := First[
FirstPosition[RH`ReadableForm`list, 
Max[RH`ReadableForm`list]]]; Condition[
PositionLargest[
Pattern[RH`ReadableForm`list, 
Blank[List]], 
Pattern[RH`ReadableForm`n, 
Alternatives[
Blank[Integer], 
HoldPattern[UpTo][
Blank[Integer]]]]], 
AllTrue[RH`ReadableForm`list, NumericQ]] := Take[
Flatten[
Map[Position[RH`ReadableForm`list, #]& , 
DeleteDuplicates[
TakeLargest[RH`ReadableForm`list, RH`ReadableForm`n]]]], RH`ReadableForm`n]], CachePersistence -> Full]]]]
Out[10]=

DynamicAlignment (4) 

ReadableForm can adjust indentation of certain parts of code in order to obtain alignment across lines when using a monospace font. Here is a helper function to display monospaced results:

In[11]:=
showMonospace[expr_] := Style[ToString[expr], PrivateFontOptions -> {"OperatorSubstitution" -> False}, FontFamily -> "Source Code Pro"];
In[12]:=
showMonospace[
 expr = Hold[
  myFunction[] := Module[{result, anotherResult, test}, result = If[
thisThing[], 
thenDoThatThing[], 
If[
thisThing[], 
thenDoThatThing[], 
otherwiseDoSomethingElse[]]]; anotherResult = If[
thisThing[], 
thenDoThatThing[], result = If[
thisThing[], 
thenDoThatThing[], 
otherwiseDoSomethingElse[]]]; otherFunction[
      result, anotherResult, test]]]]
Out[12]=

By default, ReadableForm will only indent one step at a time:

In[13]:=
ResourceFunction["ReadableForm"][expr] // showMonospace
Out[13]=

Use the setting "DynamicAlignment"True to perform additional alignment heuristics:

In[14]:=
ResourceFunction["ReadableForm"][expr, "DynamicAlignment" -> True] // showMonospace
Out[14]=

Align associations:

In[15]:=
ResourceFunction["ReadableForm"][
  TestResultObject[<|"TestClass" -> None, "TestIndex" -> 1, "TestID" -> None, "Outcome" -> "MessagesFailure", "Input" -> HoldForm[1/0], "ExpectedOutput" -> HoldForm[ComplexInfinity], "ActualOutput" -> HoldForm[ComplexInfinity], "ExpectedMessages" -> {},
     "ActualMessages" -> {
HoldForm[
Message[
MessageName[Power, "infy"], 
HoldForm[0^(-1)]]]}, "AbsoluteTimeUsed" -> Quantity[0., "Seconds"], "CPUTimeUsed" -> Quantity[0., "Seconds"], "MemoryUsed" -> Quantity[1136, "Bytes"]|>], "DynamicAlignment" -> True, PageWidth -> 150] // showMonospace
Out[15]=

FormatHeads (3) 

By default, some expressions are formatted in StandardForm in order to enhance readability:

In[16]:=
data = <|"Image" -> RandomImage[1, {5, 5}], "Time" -> Now, "Graphics" -> \!\(\*
GraphicsBox[
{RGBColor[0, 1, 0], Thickness[Large], RectangleBox[{0, -1}, {2, 1}], 
{RGBColor[1, 0, 0], DiskBox[{0, 0}]}, 
{RGBColor[0, 0, 1], CircleBox[{2, 0}]}, 
{RGBColor[1, 1, 0], PolygonBox[{{2, 0}, {4, 1}, {4, -1}}]}, 
{RGBColor[0.5, 0, 0.5], Arrowheads[Large], ArrowBox[
          NCache[{{4, Rational[3, 2]}, {0, Rational[3, 2]}, {0, 0}}, {{4, 1.5}, {0, 1.5}, {0, 0}}]], 
{GrayLevel[0], Dashing[{Small, Small}], LineBox[{{-1, 0}, {4, 0}}]}}}]\)|>;
ResourceFunction["ReadableForm"][data]
Out[12]=

Use "FormatHeads" to control which expressions are formatted in StandardForm:

In[17]:=
ResourceFunction["ReadableForm"][data, "FormatHeads" -> {Image, Graphics}]
Out[17]=

Use Automatic to include the defaults:

In[18]:=
ResourceFunction["ReadableForm"][data, "FormatHeads" -> {Automatic, Graphics}]
Out[18]=

IndentSize (1) 

Change the size of indentation in the output:

In[19]:=
ResourceFunction["ReadableForm"][SystemInformation["Small"], "IndentSize" -> 1, "FormatHeads" -> None]
Out[19]=

InitialIndent (1) 

Specify an amount of additional indentation to apply to every line:

In[20]:=
ResourceFunction[
 "ReadableForm"][<|
  "Test1" -> <|"Name" -> "First", "Data" -> Range[10]|>, "Test2" -> <|"Name" -> "Second", "Data" -> {}|>|>, "InitialIndent" -> 8]
Out[20]=

PageWidth (1) 

Set a desired page width to control line breaks:

In[21]:=
ResourceFunction["ReadableForm"][SystemInformation["Small"], "PageWidth" -> 60, "FormatHeads" -> None]
Out[21]=

PerformanceGoal (2) 

By default, ReadableForm will traverse expressions as deeply as possible to apply formatting:

In[22]:=
RepeatedTiming[
 ToString[ResourceFunction["ReadableForm"][Unevaluated[Condition[
PositionLargest[
Pattern[list, 
Blank[List]]], 
AllTrue[list, NumericQ]] := First[
FirstPosition[list, 
Max[list]]]; Condition[
PositionLargest[
Pattern[list, 
Blank[List]], 
Pattern[n, 
Alternatives[
Blank[Integer], 
HoldPattern[UpTo][
Blank[Integer]]]]], 
AllTrue[list, NumericQ]] := Take[
Flatten[
Map[Position[list, #]& , 
DeleteDuplicates[
TakeLargest[list, n]]]], n]], CachePersistence -> None]]]
Out[22]=

With PerformanceGoal"Speed", ReadableForm traverses only deeply enough to format lines and uses InputForm to handle the rest:

In[23]:=
RepeatedTiming[
 ToString[ResourceFunction["ReadableForm"][Unevaluated[Condition[
PositionLargest[
Pattern[list, 
Blank[List]]], 
AllTrue[list, NumericQ]] := First[
FirstPosition[list, 
Max[list]]]; Condition[
PositionLargest[
Pattern[list, 
Blank[List]], 
Pattern[n, 
Alternatives[
Blank[Integer], 
HoldPattern[UpTo][
Blank[Integer]]]]], 
AllTrue[list, NumericQ]] := Take[
Flatten[
Map[Position[list, #]& , 
DeleteDuplicates[
TakeLargest[list, n]]]], n]], CachePersistence -> None, PerformanceGoal -> "Speed"]]]
Out[23]=

PrefixForm (2) 

By default, ReadableForm will use Prefix formatting (f@expr) for some expressions:

In[24]:=
ResourceFunction["ReadableForm"][Unevaluated[Condition[
PositionLargest[
Pattern[list, 
Blank[List]]], 
AllTrue[list, NumericQ]] := First[
FirstPosition[list, 
Max[list]]]; Condition[
PositionLargest[
Pattern[list, 
Blank[List]], 
Pattern[n, 
Alternatives[
Blank[Integer], 
HoldPattern[UpTo][
Blank[Integer]]]]], 
AllTrue[list, NumericQ]] := Take[
Flatten[
Map[Position[list, #]& , 
DeleteDuplicates[
TakeLargest[list, n]]]], n]]]
Out[24]=

Disable Prefix formatting:

In[25]:=
ResourceFunction["ReadableForm"][Unevaluated[Condition[
PositionLargest[
Pattern[list, 
Blank[List]]], 
AllTrue[list, NumericQ]] := First[
FirstPosition[list, 
Max[list]]]; Condition[
PositionLargest[
Pattern[list, 
Blank[List]], 
Pattern[n, 
Alternatives[
Blank[Integer], 
HoldPattern[UpTo][
Blank[Integer]]]]], 
AllTrue[list, NumericQ]] := Take[
Flatten[
Map[Position[list, #]& , 
DeleteDuplicates[
TakeLargest[list, n]]]], n]], "PrefixForm" -> False]
Out[25]=

RealAccuracy (2) 

By default, ReadableForm displays real numbers with all available digits:

In[26]:=
ResourceFunction["ReadableForm"][RandomReal[1000, 5]]
Out[26]=

Specify a maximum number of digits to display:

In[27]:=
ResourceFunction["ReadableForm"][RandomReal[1000, 5], "RealAccuracy" -> 3]
Out[27]=

RelativeWidth (2) 

By default, measuring "PageWidth" per line includes the leading whitespace:

In[28]:=
expr = <|
   "Test1" -> <|"Name" -> "First", "Data" -> Range[10]|>,
   "Test2" -> <|"Name" -> "Second", "Data" -> {}|>
   |>;
ResourceFunction["ReadableForm"][expr, "IndentSize" -> 12, "PageWidth" -> 45]
Out[12]=

Set "RelativeWidth" to True if you don't want to count the leading whitespace:

In[29]:=
ResourceFunction["ReadableForm"][expr, "IndentSize" -> 12, "PageWidth" -> 45, "RelativeWidth" -> True]
Out[29]=

Applications (12) 

Copy Formatted Expressions (1) 

Copy expressions to the clipboard that will be formatted nicely when pasting into email, etc.:

In[30]:=
SystemInformation["Small"] // ResourceFunction["ReadableForm"] // CopyToClipboard;
Paste[]

Readable Log Files (1) 

Create readable log files with lots of metadata:

In[31]:=
logEvent[event_] := PutAppend[
   ResourceFunction[
    "ReadableForm"][<|"Time" -> DateString[], "Event" -> event, "Metadata" -> ResourceFunction["SessionInformation"][]|>, "FormatHeads" -> None, PageWidth -> 70], "log.txt"];
In[32]:=
logEvent["A thing just happened"]
In[33]:=
FilePrint["log.txt"]

Generate Formatted Packages (4) 

ReadableForm can be used to convert a FullDefinition into a formatted package notebook. Here's an example from a resource function:

In[34]:=
defString = Block[{$ContextPath = Prepend[$ContextPath, ResourceFunction["Stereogram3D", "Context"]]}, ToString[FullDefinition[ResourceFunction["Stereogram3D"]], InputForm]];

View the default formatting:

In[35]:=
Snippet[defString, 20]
Out[35]=

Create formatted cells using ReadableForm:

In[36]:=
cells = Cases[ToExpression[defString, InputForm, HoldComplete], e : Except[Null] :> Cell[BoxData[
      ToBoxes[ResourceFunction["ReadableForm"][Unevaluated[e]]]], "Code"]];

Display in a package notebook:

In[37]:=
NotebookPut@Notebook[cells, StyleDefinitions -> "Package.nb"]
Out[37]=

Create a Formatter Palette (6) 

Create a function to format notebook boxes:

In[38]:=
$lb = _String?(StringMatchQ[
     WhitespaceCharacter ... ~~ "\n" | FromCharacterCode[62371] ~~ WhitespaceCharacter ...]);
In[39]:=
formatBoxes[RowBox[lines : {___, $lb, ___}]] := Enclose[Riffle[(Confirm@*formatBoxes) /@ DeleteCases[lines, $lb], "\n"]];
In[40]:=
formatBoxes[boxes_] := Enclose[Replace[
    Confirm[Replace[
      MakeExpression[StripBoxes[boxes], StandardForm], _ErrorBox -> $Failed]], HoldComplete[e_] :> Replace[ToBoxes[
       ResourceFunction["ReadableForm"][Unevaluated[e], "DynamicAlignment" -> True], StandardForm], StyleBox[b_, ___] :> b]]];

Create a button that formats the boxes representing the current selection:

In[41]:=
formatSelection[nbo_NotebookObject] := Enclose[NotebookWrite[nbo, Confirm[formatBoxes[StripBoxes[NotebookRead[nbo]]]]], MessageDialog[
     "The current selection does not contain a valid expression."] &];
In[42]:=
sel = Button["Format Selection", formatSelection[InputNotebook[]], Method -> "Queued"]
Out[42]=

Create another button that formats cells:

In[43]:=
formatCell[cell_CellObject] := (
   SelectionMove[cell, All, CellContents]; formatSelection[ParentNotebook[cell]]
   );

formatCell[cells : {___CellObject}] := formatCell /@ (ResourceFunction["SelectByCurrentValue"][cells, CellStyle, MemberQ["Input" | "Code"]]);
In[44]:=
cell = Button["Format Cells", formatCell[SelectedCells[InputNotebook[]]], Method -> "Queued"]
Out[44]=

Create one more button to format an entire notebook:

In[45]:=
nb = Button["Format Notebook", formatCell[Cells[InputNotebook[]]], Method -> "Queued"]
Out[45]=

Combine into a single palette:

In[46]:=
CreatePalette[Column[{sel, cell, nb}], WindowTitle -> "Formatter", CellContext -> $Context]
Out[46]=

Test on an example notebook:

In[47]:=
NotebookOpen[FindFile["ExampleData/document.nb"]]
Out[47]=

Properties and Relations (1) 

For expressions that only require a single line to display, the output will be very similar to InputForm:

In[48]:=
InputForm[x Sqrt[5] + y^2 + 1/z]
Out[48]=
In[49]:=
ResourceFunction["ReadableForm"][x Sqrt[5] + y^2 + 1/z]
Out[49]=

Possible Issues (2) 

ReadableForm is just a formatting wrapper; it doesn't evaluate on its own:

In[50]:=
FullForm[ResourceFunction["ReadableForm"][x]]
Out[50]=

The head is not visible to InputForm:

In[51]:=
InputForm[ResourceFunction["ReadableForm"][x]]
Out[51]=

Requirements

Wolfram Language 11.3 (March 2018) or above

Version History

  • 2.1.0 – 06 May 2022
  • 2.0.0 – 19 April 2021
  • 1.0.0 – 12 October 2018

Related Resources

License Information