7

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?

xzczd
  • 65,995
  • 9
  • 163
  • 468
Kagaratsch
  • 11,955
  • 4
  • 25
  • 72

4 Answers4

9

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

Kuba
  • 136,707
  • 13
  • 279
  • 740
  • 2
    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}}]. – xzczd Nov 06 '17 at 14:35
  • Good workaround. I just found another possible workaround, not as flexible as yours, but simpler: modify MakeBoxes[MatrixForm[{lists}]] to MakeBoxes[{lists}, TraditionalForm]. – xzczd Nov 07 '17 at 04:26
  • 1
    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]. – jkuczm Nov 07 '17 at 13:11
  • @jkuczm You are right, I don't know why I'm always hesitant to use HoldAllComplete. p.s. any idea about mentioned issue? – Kuba Nov 07 '17 at 14:31
  • 1
    @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. – jkuczm Nov 07 '17 at 15:00
  • @jkuczm yep, I meant the latter. The problem is I don't know why it happens and if it should happen. Thanks for tips anyway. – Kuba Nov 07 '17 at 15:04
  • 4
    @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. – jkuczm Nov 07 '17 at 17:09
  • @jkuczm I guess it is worth reporting. Thanks for investigation. – Kuba Nov 07 '17 at 18:16
6

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

jkuczm
  • 15,078
  • 2
  • 53
  • 84
  • 1
    Instead of including TooltipBox in the DisplayFunction, you could add the option Tooltip->"Obj" to the TemplateBox. – Carl Woll Nov 07 '17 at 15:44
  • @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. – jkuczm Nov 07 '17 at 15:55
  • This is neat. There are pieces of this I can use on some stuff I'm working on. Thank you! – J. M.'s missing motivation Nov 07 '17 at 21:20
5

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.

J. M.'s missing motivation
  • 124,525
  • 11
  • 401
  • 574
4

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.

Kagaratsch
  • 11,955
  • 4
  • 25
  • 72