6
$\begingroup$

Let's say I have a custom object that I denote

Obj[a,b,c,...]

There are always n^2 entries in the object for some integer n, and I'd like it to appear as a matrix once it is entered. For example, I'd like to get the following output:

Obj[x11,x12,x21,x22]

enter image description here

and

Obj[x11,x12,x13,x21,x22,x23,x31,x32,x33]

enter image description here

And of course it should also just give the other way around:

enter image description here

Obj[x11,x12,x21,x22]

I guess the biggest challenge is to make it work for any n automatically. Is it possible to do that in Mathematica?

$\endgroup$
8
$\begingroup$

InterpretationBox will take care of making safe round trip from boxes to expression but we need to take extra care during condition checking and partition not to evaluate Obj's arguments.

It does not matter that Obj is not holding them, Obj itself can be held. Hold @ Obj[1,2,3,4] etc.

Edit to the old code

As xzczd has noticed, an Input cell with e.g. Obj[1,2,3,4] shows explicit MatrixForm after Ctrl+Shit+N.

Which is strange, the more that I can't reproduce that with combinations of NotebookRead/MakeExpression/MakeBoxes etc.

The problem seems to be caused by TagBox so we can take even more extra care and work around it:

Obj /: MakeBoxes[ 
  o : Obj[args__], StandardForm
] /; IntegerQ @ Sqrt @ Length @ Unevaluated @ args := With[
  { array = (
      List @@@ Partition[Hold[args], Sqrt@Length[Unevaluated[args]]]
    ) /. Hold[lists__] :> Map[
      Function[x, MakeBoxes[x, StandardForm], HoldFirst]
    , Unevaluated @ {lists}
    , {2}
    ]
  }
, InterpretationBox[
    RowBox[{ "(", "\[NoBreak]"
    , GridBox[array, RowSpacings -> 1, ColumnSpacings -> 1
      , RowAlignments -> Baseline, ColumnAlignments -> Center
      ]
    , "\[NoBreak]", ")"
    }]
  , o
  ]
]

Old

Obj /: MakeBoxes[
  o : Obj[args__], StandardForm
] /; IntegerQ @ Sqrt @ Length[Unevaluated[args]] := With[
  { matrixFormBoxes = (
      List @@@ Partition[Hold[args], Sqrt @ Length[Unevaluated[args]]]
    ) /. Hold[lists__] :> MakeBoxes[MatrixForm[{lists}]]
  }
, InterpretationBox[
    matrixFormBoxes
  , o
  ]
]

Obj[1, 2, 3, 4]
Obj[1, 2, 4]
Hold @ Obj[Echo[1], 2, 3, 4, 5, 6, 7, 8, 9]

enter image description here

% // ReleaseHold

enter image description here

% // FullForm
Obj[1,2,3,4,5,6,7,8,9]

related:

https://mathematica.stackexchange.com/a/149668/5478

$\endgroup$
  • 2
    $\begingroup$ One issue remained: if one writes e.g. Obj[1, 2, 3, 4] in an input cell, and press Ctrl+Shift+N, then the code will display as MatrixForm[{{x11, x12, x13}, {x21, x22, x23}, {x31, x32, x33}}]. $\endgroup$ – xzczd Nov 6 '17 at 14:35
  • $\begingroup$ Good workaround. I just found another possible workaround, not as flexible as yours, but simpler: modify MakeBoxes[MatrixForm[{lists}]] to MakeBoxes[{lists}, TraditionalForm]. $\endgroup$ – xzczd Nov 7 '17 at 4:26
  • 1
    $\begingroup$ As to evaluation leaks, box formatting functions should, in my opinion, always use "strong" HoldAllComplete-type holding, to avoid nasty things like: Hold[___, nasty, ___] ^:= Print@"Leak!"; HoldComplete@Obj[1, 2, nasty, 3]. $\endgroup$ – jkuczm Nov 7 '17 at 13:11
  • 1
    $\begingroup$ @Kuba Just use HoldComplete instead of Hold and HoldAllComplete instead of HoldFirst. Or did you mean the issue mentioned by xzczd? Then I don't have anything better than what is already in your answer. $\endgroup$ – jkuczm Nov 7 '17 at 15:00
  • 3
    $\begingroup$ @Kuba Ctrl+Shift+N calls BoxForm`ConvertForm[BoxData[RowBox[{"Obj", "[", RowBox[{"1", ",", "2", ",", "3", ",", "4"}], "]"}]], StandardForm, StandardForm, "Input"]. It seems that MakeBoxes called by BoxForm`ConvertForm somehow ignores standard formatting of builtin ...Form wrappers and converts MatrixForm[...] to RowBox[{"MatrixForm", "[", ..., "]"}] instead of ordinary GridBox. I don't know how it happens. $\endgroup$ – jkuczm Nov 7 '17 at 17:09
6
$\begingroup$

InterpretationBox used in answer by J.M. and by Kuba allows you to copy formatted object and keep its proper interpretation as an Obj expression, but you can't edit such formatted objects. You could make InterpretationBox editable by using Editable -> True option but this will allow you to just edit formatted version while interpretation would incorrectly remain unchanged.

To make copied formatted object editable, instead of InterpretationBox, you could use TemplateBox with appropriate DisplayFunction:

Obj // ClearAll
Obj /: MakeBoxes[Obj@args__, StandardForm] := Module[{n, k}, 
    n = Length@Unevaluated@args;
    k = Sqrt@n;
    TemplateBox[
        MakeBoxes /@ Unevaluated@{args},
        "Obj",
        Tooltip -> "Obj",
        DisplayFunction -> (Evaluate@RowBox@{"(", "\[NoBreak]", GridBox[
            Partition[Array[Slot, n], k],
            RowSpacings -> 1, ColumnSpacings -> 1,
            RowAlignments -> Baseline, ColumnAlignments -> Center
        ], "\[NoBreak]", ")"}&)
    ] /; IntegerQ@k
]

Example of copying and editing of formatted object:

Obj[1, 2, 3, 4]

Obj copying and editing

$\endgroup$
  • 1
    $\begingroup$ Instead of including TooltipBox in the DisplayFunction, you could add the option Tooltip->"Obj" to the TemplateBox. $\endgroup$ – Carl Woll Nov 7 '17 at 15:44
  • $\begingroup$ @CarlWoll thanks, changed. I had it in DisplayFunction because originally tooltip was RowBox@{"Obj", "[", RowBox@Riffle[Array[Slot, n], ","], "]"}, but it didn't update after editing formatted expression, so I changed it to a "constant" tooltip. $\endgroup$ – jkuczm Nov 7 '17 at 15:55
  • $\begingroup$ This is neat. There are pieces of this I can use on some stuff I'm working on. Thank you! $\endgroup$ – J. M. will be back soon Nov 7 '17 at 21:20
5
$\begingroup$

Something to start with:

Obj /: MakeBoxes[Obj[args__], form : StandardForm] := 
       With[{arr = Partition[{args}, Sqrt[Length[{args}]]]}, 
            With[{boxes = MakeBoxes[MatrixForm[arr], form]}, 
                 InterpretationBox[boxes, Obj[args]]]]

I make no attempt to check if the number of arguments is a square number; you can add that check yourself, if you wish.

$\endgroup$
  • $\begingroup$ This will leak evaluation. e.g. Hold@Obj[Echo[1], 2, 3, 4] $\endgroup$ – Kuba Nov 6 '17 at 14:24
  • $\begingroup$ @Kuba what does that mean? Is it bad? EDIT: Removed the accept for now, to make sure I selected the best answer. $\endgroup$ – Kagaratsch Nov 6 '17 at 14:26
  • $\begingroup$ @Kagaratsch, yes, this is why you shouldn't accept answers too quickly. ;) If you're concerned about leaks, then Kuba's approach is certainly better than mine. $\endgroup$ – J. M. will be back soon Nov 6 '17 at 15:06
3
$\begingroup$

Here is my solution:

Format[Obj[x__]] := MatrixForm[Partition[{x}, Sqrt[Length[{x}]]]]

It seems short and nice, but the solution by J.M. or Kuba is better for the following reason. If we enter an object, then copy-and-paste the output and try FullForm, on my objects above we get:

enter image description here

while the objects of J.M. or Kuba properly give:

enter image description here

I wonder why Format is not implemented a bit more sophisticated? As a naive end-consumer I'd expect it to take the right hand side as a pattern, not directly use it, but go into the box structure and make necessary adjustments automatically to make sure that under any circumstances where the output is copied or transformed to input form, the correct initial input is recovered.

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