Make Display and Debug outputs concise and consistent
Summary
- Removed type names from Display implementations (they remain in/were added to Debug)
- Changed vector-based geometry types (Translation, Scale) to display on a single line by default (Was already the case for Point). Pretty-printing as a multiline vector can be enabled using 'alternative' formatting i.e. println!("{point:#}")
- Consistently wrapped struct displays in {} and vector displays in []
- Precision parameter now applies to all geometry types e.g. println!("{point:.2}")
- Other formatting parameters such as padding apply recursively to everything that is not pretty-printed using the main multiline printing macro
- Quaternions now display as real + imaginary components i.e. 2 + 3i + 4j + 1k and debug as {x: 2, y: 3, z: 4, w: 1} (Unit and UnitDualQuaternions still display as angle & axis for rotation)
- Removed trailing newlines from matrix formatting - previously, formatting matrices would add 2 newlines after the printing of the bottom of the matrix. This reduces flexibility and takes up more vertical space than necessary, though I'd be happy to let this go.
Differences when printing (expand)
| Before | After |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
I definitely like the new Display without type names, as well as usage of # for expanded form. Few questions though:
- Why is e.g. Scale normal using
[1.0, 2.0]but Scale alternative[1, 2](the.0part)? Shouldn't it be either the same or even the other way around? Ideally we should just use normal display formatting for numbers, which would show1, 2for both. - Matrix - normal feels a bit inconsistent compared to other examples, as in, if we use
#for multiline format, it seems natural for "normal" variant to use single-line form of matrix too. Maybe something like{{1,2,3}, {4,5,6}}. Or maybe it's too much of a breaking change to introduce? - Shouldn't Matrix debug form show its type name?
- What about debug alternate form, aka
{:#?}? Haven't seen examples of those.
Thanks for working on this @owenbrooks!
I'm very short on time today but I wanted to make some initial comments; I will probably have more later, when I have the time.
I'll restrict my comments to the Debug output of Matrix right now, because it is horribly broken and problematic at the moment, see also #1071. The crux of the issue is that it defers to the Debug impl of its storage, which is deeply problematic. Your example with Matrix uses a statically allocated matrix (e.g. with matrix!), but you'll see that if you print a dynamically allocated matrix the output is wildly different.
It would be great if we could resolve this while we're at it, as the resolution may impact the other geometric types as well.
In short:
- printing of dynamically allocated matrices will just default to an inferred
Debugimpl ofVecStorage(see #1071 for an example). - printing of statically allocated matrices ends up using the
Debugoutput ofArrayStorage, which defers to theDebugimpl of its internal array. But this array is column-major! Which is very confusing and unexpected, and we've had many users both on Discord and in various GitHub issues be confused about this fact.
To resolve both these issues, I suggest that we use the same syntax that we use for matrix construction - through matrix! and dmatrix!. That is, we delimit columns by commas and rows by semi-colons. This has several benefits:
- You can directly take the debug output and copy-paste it into your code, using
matrix!ordmatrix!. For me this is a big deal, because it's something I do quite often (for example create a unit test from a failing real-world example). - Delimiting by semi-colons means that we can print any matrix on a single line by default, and use
#for multi-line formatting, which I believe resolves one of @RReverser's concerns.
Note that this means that vectors will always print as e.g. [1; 2; 3], since they are matrices with dimensions Nx1. This appears to be the right thing to do in any case, because we cannot distinguish between a Nx1 matrix and a column vector, because they are literally the same type.
Finally, I think there should not be a type name for the matrix in the debug output. Vec also does not include its own name. It's usually obvious from the context that we are working with a matrix/vector, so it's not necessary to explicitly tag it as such, and indeed tagging it makes the debug output more verbose, which is problematic in practice when you are printing large amounts of data or complex structs. This same argument does not necessarily hold for all of the geometry types, however.
- Why is e.g. Scale normal using
[1.0, 2.0]but Scale alternative[1, 2](the.0part)? Shouldn't it be either the same or even the other way around? Ideally we should just use normal display formatting for numbers, which would show1, 2for both.
Good point. It was forwarding to Debug for normal but using iterating through the values and using write! instead gets that normal display formatting now.
- Matrix - normal feels a bit inconsistent compared to other examples, as in, if we use
#for multiline format, it seems natural for "normal" variant to use single-line form of matrix too. Maybe something like{{1,2,3}, {4,5,6}}. Or maybe it's too much of a breaking change to introduce?
My feeling is that as a matrix library its nice to see matrices multi-line by default, and these vector-based types are an exception where single line is more convenient, but the alternate form is still there if needed.
- Shouldn't Matrix debug form show its type name?
True, I've added that now.
- What about debug alternate form, aka
{:#?}? Haven't seen examples of those.
Here they are:
Point - debug alternate
Point [
1.12345678,
2.12345678,
3.12345678,
]
Scale - debug alternate
Scale [
1.0,
2.0,
]
Translation - debug alternate
Translation [
1.0,
2.0,
3.0,
]
Matrix - debug alternate
Matrix [
[
1.2,
3.1,
],
[
2.4,
4.5,
],
]
UnitQuaternion - debug alternate
Quaternion {
x: 0.0,
y: 1.0,
z: 0.0,
w: 6.123233995736766e-17,
}
Isometry - debug alternate
Isometry {
rotation: Quaternion {
x: 0.0,
y: 1.0,
z: 0.0,
w: 6.123233995736766e-17,
},
translation: Translation [
1.0,
2.0,
3.0,
],
}
UnitComplex - debug alternate
Complex {
re: 0.9887710779360422,
im: 0.14943813247359922,
}
Similarity - debug alternate
Similarity {
isometry: Isometry {
rotation: Quaternion {
x: 0.0,
y: 1.0,
z: 0.0,
w: 6.123233995736766e-17,
},
translation: Translation [
1.0,
2.0,
3.0,
],
},
scaling: 0.1,
}
Quaternion - debug alternate
Quaternion {
x: 2.0,
y: 3.0,
z: 4.0,
w: 1.0,
}
UnitDualQuaternion - debug alternate
DualQuaternion {
real: Quaternion {
x: 0.0,
y: 1.0,
z: 0.0,
w: 6.123233995736766e-17,
},
dual: Quaternion {
x: 0.0,
y: 0.0,
z: 0.0,
w: 0.0,
},
}
- You can directly take the debug output and copy-paste it into your code, using
matrix!ordmatrix!. For me this is a big deal, because it's something I do quite often (for example create a unit test from a failing real-world example).
Oh yes, it's something I definitely wanted in the past but wasn't sure if in scope of this PR. Being able to copy-paste debug output into matrix construction would be a big deal. Perhaps we could follow the same for point & vector too.
@RReverser: vectors are matrices, so they'd be printed as e.g. [1; 2; 3], which is not currently compatible with the vector! macro (but you could use the matrix! macro). However, we could extend the (d)vector! macros to accept that syntax too.
Sorry, I didn't mean to suggest that you have to resolve #1071 in this PR - we don't have to increase the scope. We could also do it in several stages, as separate PRs, but in any case I think it's worth thinking about to make sure that everything ends up consistent in the end!
@RReverser: vectors are matrices, so they'd be printed as e.g.
[1; 2; 3], which is not currently compatible with thevector!macro (but you could use thematrix!macro). However, we could extend the(d)vector!macros to accept that syntax too.
Ah right, that's unfortunate. Ideally vector! would both accept and distinguish between those two forms so that vector![1,2,3] would produce a RowVector, but vector![1;2;3] would produce a (column) Vector, but that would be, again, a breaking change :(
@RReverser: vectors are matrices, so they'd be printed as e.g.
[1; 2; 3], which is not currently compatible with thevector!macro (but you could use thematrix!macro). However, we could extend the(d)vector!macros to accept that syntax too.Ah right, that's unfortunate. Ideally
vector!would both accept and distinguish between those two forms so thatvector![1,2,3]would produce a RowVector, butvector![1;2;3]would produce a (column) Vector, but that would be, again, a breaking change :(
What you're describing is exactly the behavior of matrix! ;-)
vector! really only exists to make it more explicit that you're building a (column) vector, because vectors and matrices often serve different purposes in applications.
What you're describing is exactly the behavior of
matrix!;-)
vector!really only exists to make it more explicit that you're building a (column) vector, because vectors and matrices often serve different purposes in applications.
That's fair. I guess the suggestion to output macro form applies only to {d}matrix! and point! then.
@owenbrooks
My feeling is that as a matrix library its nice to see matrices multi-line by default, and these vector-based types are an exception where single line is more convenient, but the alternate form is still there if needed.
This is reasonable, but IMO consistency is more important. It's really weird for one type to have exactly the opposite behavior of the others. I agree with @Andlon that we should standardize on "alternate form" meaning fancy 2D layout, which is also consistent with the stdlib use of it in Debug to mean "pretty print".
This is reasonable, but IMO consistency is more important. It's really weird for one type to have exactly the opposite behavior of the others. I agree with @Andlon that we should standardize on "alternate form" meaning fancy 2D layout, which is also consistent with the stdlib use of it in
Debugto mean "pretty print".
Wanting consistency between the types does make sense. I would lean towards 2D printing by default for Display as it works currently in nalgebra (and making Point consistent with this).
Looking at how printing is handled in projects like numpy, eigen, ndarray, matlab, R, they all print matrices multi line at least by default (though vectors tend to be row vectors and so take up a single line). Since nalgebra is focussed on 2D matrices, printing in 2D feels like a very canonical string representation. It's harder to see 2D patterns when the matrix is squished onto one line.
If we make the Debug output semicolon-delimited row-wise would that be usable enough for the case when single-line is desirable? Could do without alternate in that case. Otherwise, could switch it around so that “alternate” forces single line for Display.
@Andlon I will undo the addition of "Matrix " to debug output. I will also take a look at fixing that matrix Debug, it may become a separate PR.
and making Point consistent with this
I've said this before, but I'd really want to keep single-line Points for short Display. The fact they're columns in nalgebra feels almost like implementation detail, since in most contexts people would just want to see their coordinates.
If we make the Debug output semicolon-delimited row-wise would that be usable enough for the case when single-line is desirable?
That suits me fine, at least. Having an alternate form mode that's exactly equivalent to Debug feels a bit superfluous, and I don't care which is which so long as there's consistency and a concise form is readily available.
@RReverser
I'd really want to keep single-line Points for short Display
Are your requirements not addressed by just using Debug? I have little to no interest in the 2D representations myself but I'm perfectly happy just using Debug to resolve that. I feel like it would be a bit inconsistent for Vector to be vertical while a Point with the same data is horizontal, and idiomatic nalgebra geometric code will mix both heavily, so giving a concise form for only one seems of limited use.
I feel like it would be a bit inconsistent for
Vectorto be vertical while aPointwith the same data is horizontal
Personally, I think that's fine since semantically they're different types. If anything, I agree that consistency is crucial for Debug formatting, but Display is supposed to be easily human-readable first and foremost, and for that it should have some leeway to write whatever makes most sense in the context.
Debug would be fine for a workaround, but we are adding type names consistently to all types, and it would be more verbose than Display. That's expected, Debug is pretty much always meant to be the more verbose option, but it also means we should leverage Display for the more readable option.
I've reworked it a bit as follows:
- Display is 2D pretty-print by default and does not include type names.
- Display alternate prints matrices and geometry-vector types on a single line. It does not include the type name.
- Matrix/vector rows are semicolon-delimited e.g. [1.0, 2.0; 3.0, 4.0],
- Geometry-vector types (Point, Translation, and Scale) are only comma-delimited e.g. [0.1, 0.2, 0.3]
- Debug output includes type names and dimensionality and is mostly derived. However, it doesn't forward matrix Debug directly to Storage, instead it prints matrix data with semicolon-delimited rows. This does make Debug output more verbose than previously for static matrices, but it ensures all information is there and consistent between types, and there is now the concise option using Display alternate.
Requires a couple of changes to tests still.
This should fix #1071 and #898
New format output
==================
Point
==================
Normal
┌ ┐
│ 1.12345678 │
│ 2.12345678 │
│ 3.12345678 │
└ ┘
Alternate
[1.12345678, 2.12345678, 3.12345678]
Debug
OPoint { coords: Matrix { data: [1.12345678; 2.12345678; 3.12345678], nrows: 3, ncols: 1 } }
Debug alternate
OPoint {
coords: Matrix {
data: [1.12345678;
2.12345678;
3.12345678],
nrows: 3,
ncols: 1
},
}
==================
Scale
==================
Normal
┌ ┐
│ 1 │
│ 2 │
└ ┘
Alternate
[1, 2]
Debug
Scale { vector: Matrix { data: [1.0; 2.0], nrows: 2, ncols: 1 } }
Debug alternate
Scale {
vector: Matrix {
data: [1.0;
2.0],
nrows: 2,
ncols: 1
},
}
==================
Translation
==================
Normal
┌ ┐
│ 1 │
│ 2 │
│ 3 │
└ ┘
Alternate
[1, 2, 3]
Debug
Translation { vector: Matrix { data: [1.0; 2.0; 3.0], nrows: 3, ncols: 1 } }
Debug alternate
Translation {
vector: Matrix {
data: [1.0;
2.0;
3.0],
nrows: 3,
ncols: 1
},
}
==================
Matrix
==================
Normal
┌ ┐
│ 1.2 2.4 │
│ 3.1 4.5 │
└ ┘
Alternate
[1.2, 2.4; 3.1, 4.5]
Debug
Matrix { data: [1.2, 2.4; 3.1, 4.5], nrows: 2, ncols: 2 }
Debug alternate
Matrix {
data: [1.2, 2.4;
3.1, 4.5],
nrows: 2,
ncols: 2
}
==================
UnitQuaternion
==================
Normal
{ angle: 3.141592653589793, axis: (0, 1, 0) }
Alternate
{ angle: 3.141592653589793, axis: (0, 1, 0) }
Debug
Quaternion { x: 0.0, y: 1.0, z: 0.0, w: 6.123233995736766e-17 }
Debug alternate
Quaternion {
x: 0.0,
y: 1.0,
z: 0.0,
w: 6.123233995736766e-17,
}
==================
Isometry
==================
Normal
{ translation: [1, 2, 3], rotation: { angle: 3.141592653589793, axis: (0, 1, 0) } }
Debug
Isometry { rotation: Quaternion { x: 0.0, y: 1.0, z: 0.0, w: 6.123233995736766e-17 }, translation: Translation { vector: Matrix { data: [1.0; 2.0; 3.0], nrows: 3, ncols: 1 } } }
Debug alternate
Isometry {
rotation: Quaternion {
x: 0.0,
y: 1.0,
z: 0.0,
w: 6.123233995736766e-17,
},
translation: Translation {
vector: Matrix {
data: [1.0;
2.0;
3.0],
nrows: 3,
ncols: 1
},
},
}
==================
UnitComplex
==================
Normal
UnitComplex angle: 0.15
Debug
Complex { re: 0.9887710779360422, im: 0.14943813247359922 }
Debug alternate
Complex {
re: 0.9887710779360422,
im: 0.14943813247359922,
}
==================
Similarity
==================
Normal
{ translation: [1, 2, 3], rotation: { angle: 3.141592653589793, axis: (0, 1, 0) }, scaling: 0.1 }
Debug
Similarity { isometry: Isometry { rotation: Quaternion { x: 0.0, y: 1.0, z: 0.0, w: 6.123233995736766e-17 }, translation: Translation { vector: Matrix { data: [1.0; 2.0; 3.0], nrows: 3, ncols: 1 } } }, scaling: 0.1 }
Debug alternate
Similarity {
isometry: Isometry {
rotation: Quaternion {
x: 0.0,
y: 1.0,
z: 0.0,
w: 6.123233995736766e-17,
},
translation: Translation {
vector: Matrix {
data: [1.0;
2.0;
3.0],
nrows: 3,
ncols: 1
},
},
},
scaling: 0.1,
}
==================
Quaternion
==================
Normal
2 + 3i + 4j + 1k
Debug
Quaternion { x: 2.0, y: 3.0, z: 4.0, w: 1.0 }
Debug alternate
Quaternion {
x: 2.0,
y: 3.0,
z: 4.0,
w: 1.0,
}
==================
UnitDualQuaternion
==================
Normal
{ translation: [0, 0, 0], rotation: { angle: 3.141592653589793, axis: (0, 1, 0) } }
Debug
DualQuaternion { real: Quaternion { x: 0.0, y: 1.0, z: 0.0, w: 6.123233995736766e-17 }, dual: Quaternion { x: 0.0, y: 0.0, z: 0.0, w: 0.0 } }
Debug alternate
DualQuaternion {
real: Quaternion {
x: 0.0,
y: 1.0,
z: 0.0,
w: 6.123233995736766e-17,
},
dual: Quaternion {
x: 0.0,
y: 0.0,
z: 0.0,
w: 0.0,
},
}
Old output for reference
==================
Point
==================
Normal
{1.12345678, 2.12345678, 3.12345678}
Alternate
{1.12345678, 2.12345678, 3.12345678}
Debug
[1.12345678, 2.12345678, 3.12345678]
Debug alternate
[
1.12345678,
2.12345678,
3.12345678,
]
==================
Scale
==================
Normal
Scale {
┌ ┐
│ 1.000 │
│ 2.000 │
└ ┘
}
Alternate
Scale {
┌ ┐
│ 1.000 │
│ 2.000 │
└ ┘
}
Debug
[1.0, 2.0]
Debug alternate
[
1.0,
2.0,
]
==================
Translation
==================
Normal
Translation {
┌ ┐
│ 1.000 │
│ 2.000 │
│ 3.000 │
└ ┘
}
Alternate
Translation {
┌ ┐
│ 1.000 │
│ 2.000 │
│ 3.000 │
└ ┘
}
Debug
[1.0, 2.0, 3.0]
Debug alternate
[
1.0,
2.0,
3.0,
]
==================
Matrix
==================
Normal
┌ ┐
│ 1.2 2.4 │
│ 3.1 4.5 │
└ ┘
Alternate
┌ ┐
│ 1.2 2.4 │
│ 3.1 4.5 │
└ ┘
Debug
[[1.2, 3.1], [2.4, 4.5]]
Debug alternate
[
[
1.2,
3.1,
],
[
2.4,
4.5,
],
]
==================
UnitQuaternion
==================
Normal
UnitQuaternion angle: 3.141592653589793 − axis: (0, 1, 0)
Alternate
UnitQuaternion angle: 3.141592653589793 − axis: (0, 1, 0)
Debug
[0.0, 1.0, 0.0, 6.123233995736766e-17]
Debug alternate
[
0.0,
1.0,
0.0,
6.123233995736766e-17,
]
==================
Isometry
==================
Normal
Isometry {
Translation {
┌ ┐
│ 1.000 │
│ 2.000 │
│ 3.000 │
└ ┘
}
UnitQuaternion angle: 3.141592653589793 − axis: (0, 1, 0)}
Debug
Isometry { rotation: [0.0, 1.0, 0.0, 6.123233995736766e-17], translation: [1.0, 2.0, 3.0] }
Debug alternate
Isometry {
rotation: [
0.0,
1.0,
0.0,
6.123233995736766e-17,
],
translation: [
1.0,
2.0,
3.0,
],
}
==================
UnitComplex
==================
Normal
UnitComplex angle: 0.15
Debug
Complex { re: 0.9887710779360422, im: 0.14943813247359922 }
Debug alternate
Complex {
re: 0.9887710779360422,
im: 0.14943813247359922,
}
==================
Similarity
==================
Normal
Similarity {
Isometry {
Translation {
┌ ┐
│ 1.000 │
│ 2.000 │
│ 3.000 │
└ ┘
}
UnitQuaternion angle: 3.141592653589793 − axis: (0, 1, 0)}
Scaling: 0.100}
Debug
Similarity { isometry: Isometry { rotation: [0.0, 1.0, 0.0, 6.123233995736766e-17], translation: [1.0, 2.0, 3.0] }, scaling: 0.1 }
Debug alternate
Similarity {
isometry: Isometry {
rotation: [
0.0,
1.0,
0.0,
6.123233995736766e-17,
],
translation: [
1.0,
2.0,
3.0,
],
},
scaling: 0.1,
}
==================
Quaternion
==================
Normal
Quaternion 1 − (2, 3, 4)
Debug
[2.0, 3.0, 4.0, 1.0]
Debug alternate
[
2.0,
3.0,
4.0,
1.0,
]
==================
UnitDualQuaternion
==================
Normal
UnitDualQuaternion translation:
┌ ┐
│ 0 │
│ 0 │
│ 0 │
└ ┘
− angle: 3.141592653589793 − axis: (0, 1, 0)
Debug
DualQuaternion { real: [0.0, 1.0, 0.0, 6.123233995736766e-17], dual: [0.0, 0.0, 0.0, 0.0] }
Debug alternate
DualQuaternion {
real: [
0.0,
1.0,
0.0,
6.123233995736766e-17,
],
dual: [
0.0,
0.0,
0.0,
0.0,
],
}
New format: static/dynamic matrices, ArrayStorage and VecStorage
==========================
Static Matrix
==========================
Display
┌ ┐
│ 1 2 3 4 │
│ 5 6 7 8 │
└ ┘
Display Alternate
[1, 2, 3, 4; 5, 6, 7, 8]
Debug
Matrix { data: [1.0, 2.0, 3.0, 4.0; 5.0, 6.0, 7.0, 8.0], nrows: 2, ncols: 4 }
Alternate
Matrix {
data: [1.0, 2.0, 3.0, 4.0;
5.0, 6.0, 7.0, 8.0],
nrows: 2,
ncols: 4
}
==========================
Dynamic Matrix
==========================
Display
┌ ┐
│ 1 2 3 4 │
│ 5 6 7 8 │
└ ┘
Display Alternate
[1, 2, 3, 4; 5, 6, 7, 8]
Debug
Matrix { data: [1.0, 2.0, 3.0, 4.0; 5.0, 6.0, 7.0, 8.0], nrows: 2, ncols: 4 }
Debug Alternate
Matrix {
data: [1.0, 2.0, 3.0, 4.0;
5.0, 6.0, 7.0, 8.0],
nrows: 2,
ncols: 4
}
==========================
ArrayStorage
==========================
Debug (derived -> column-major)
ArrayStorage([[1.0, 5.0], [2.0, 6.0], [3.0, 7.0], [4.0, 8.0]])
Display not implemented.
==========================
VecStorage
==========================
Debug (derived -> column-major)
VecStorage { data: [1.0, 5.0, 2.0, 6.0, 3.0, 7.0, 4.0, 8.0], nrows: Dynamic { value: 2 }, ncols: Dynamic { value: 4 } }
Display not implemented.
Old format: static/dynamic matrices, ArrayStorage and VecStorage
==========================
Static Matrix
==========================
Display
┌ ┐
│ 1 2 3 4 │
│ 5 6 7 8 │
└ ┘
Display Alternate
┌ ┐
│ 1 2 3 4 │
│ 5 6 7 8 │
└ ┘
Debug
[[1.0, 5.0], [2.0, 6.0], [3.0, 7.0], [4.0, 8.0]]
Alternate
[
[
1.0,
5.0,
],
[
2.0,
6.0,
],
[
3.0,
7.0,
],
[
4.0,
8.0,
],
]
==========================
Dynamic Matrix
==========================
Display
┌ ┐
│ 1 2 3 4 │
│ 5 6 7 8 │
└ ┘
Display Alternate
┌ ┐
│ 1 2 3 4 │
│ 5 6 7 8 │
└ ┘
Debug
VecStorage { data: [1.0, 5.0, 2.0, 6.0, 3.0, 7.0, 4.0, 8.0], nrows: Dynamic { value: 2 }, ncols: Dynamic { value: 4 } }
Debug Alternate
VecStorage {
data: [
1.0,
5.0,
2.0,
6.0,
3.0,
7.0,
4.0,
8.0,
],
nrows: Dynamic {
value: 2,
},
ncols: Dynamic {
value: 4,
},
}
==========================
ArrayStorage
==========================
Debug (derived -> column-major)
[[1.0, 5.0], [2.0, 6.0], [3.0, 7.0], [4.0, 8.0]]
Display not implemented.
==========================
VecStorage
==========================
Debug (derived -> column-major)
VecStorage { data: [1.0, 5.0, 2.0, 6.0, 3.0, 7.0, 4.0, 8.0], nrows: Dynamic { value: 2 }, ncols: Dynamic { value: 4 } }
Display not implemented.
This Debug formatting is a major regression for the common case. This is important due to e.g. dbg. Please use the single-line semicolon-delimited form, and omit type names for Matrix and single-element wrappers thereof. See also precedent in Vec, as discussed previously.
Perhaps the strongest argument in favor of a readable, concise Debug implementation is the widespread use of derive(Debug) in downstream code, where concise formatting on nalgebra's part can be make-or-break for an aggergate value being usable at all.
Yep that makes a lot of sense. Thanks for your explanation! How is this:
==========================
Display
┌ ┐
│ 1 2 3 4 │
│ 5 6 7 8 │
└ ┘
Display Alternate
[1, 2, 3, 4; 5, 6, 7, 8]
Debug
[1.0, 2.0, 3.0, 4.0; 5.0, 6.0, 7.0, 8.0]
Debug Alternate
[1.0, 2.0, 3.0, 4.0;
5.0, 6.0, 7.0, 8.0]
dbg!(dmat)
[src/bin/matrix_print.rs:24] dmat =
[1.0, 2.0, 3.0, 4.0;
5.0, 6.0, 7.0, 8.0]
Thanks, I'm much happier with that (and I appreciate your patience working through all these drafts!). I like what you did with the Debug Alternate format too; that's a nice middle ground, and consistent with the spirit of how it's used in general.
I am a bit confused as to why the default precision is different between Debug and Display. Is that consistent with the impls for f32? If not, should we really be behaving differently?
Thanks, I'm much happier with that (and I appreciate your patience working through all these drafts!). I like what you did with the Debug Alternate format too; that's a nice middle ground, and consistent with the spirit of how it's used in general.
I am a bit confused as to why the default precision is different between
DebugandDisplay. Is that consistent with the impls forf32? If not, should we really be behaving differently?
Yep, it's just passing through all the formatting options to the underlying float formatter. My understanding is that for floating point Display, when precision is not specified, it tries to create the shortest possible string, omitting the decimal for values like 4.00. It's similar for Debug but the minimum precision is set to 1. See https://doc.rust-lang.org/src/core/fmt/float.rs.html#187
Hmm, I've just realised that this does mean that not everything will be perfectly aligned in debug alternate. My feeling is that is acceptable since Display is handling this. E.g.
Display
┌ ┐
│ 1.243 2 3 4 │
│ 5 6 7 8 │
└ ┘
Display Alternate
[1.243, 2, 3, 4; 5, 6, 7, 8]
Debug
[1.243, 2.0, 3.0, 4.0; 5.0, 6.0, 7.0, 8.0]
Debug Alternate
[1.243, 2.0, 3.0, 4.0;
5.0, 6.0, 7.0, 8.0]
[src/bin/matrix_print.rs:24] dmat =
[1.243, 2.0, 3.0, 4.0;
5.0, 6.0, 7.0, 8.0]
New output
==================
Point
==================
Normal
┌ ┐
│ 1.12345678 │
│ 2.12345678 │
│ 3.12345678 │
└ ┘
Alternate
[1.12345678, 2.12345678, 3.12345678]
Debug
[1.12345678, 2.12345678, 3.12345678]
Debug alternate
[
1.12345678,
2.12345678,
3.12345678,
]
==================
Scale
==================
Normal
┌ ┐
│ 1 │
│ 2 │
└ ┘
Alternate
[1, 2]
Debug
[1.0, 2.0]
Debug alternate
[
1.0,
2.0,
]
==================
Translation
==================
Normal
┌ ┐
│ 1 │
│ 2 │
│ 3 │
└ ┘
Alternate
[1, 2, 3]
Debug
[1.0, 2.0, 3.0]
Debug alternate
[
1.0,
2.0,
3.0,
]
==================
Matrix
==================
Normal
┌ ┐
│ 1.2 2.4 │
│ 3.1 4.5 │
└ ┘
Alternate
[1.2, 2.4; 3.1, 4.5]
Debug
[1.2, 2.4; 3.1, 4.5]
Debug alternate
[1.2, 2.4;
3.1, 4.5]
==================
UnitQuaternion
==================
Normal
{ angle: 3.141592653589793, axis: (0, 1, 0) }
Alternate
{ angle: 3.141592653589793, axis: (0, 1, 0) }
Debug
Quaternion { x: 0.0, y: 1.0, z: 0.0, w: 6.123233995736766e-17 }
Debug alternate
Quaternion {
x: 0.0,
y: 1.0,
z: 0.0,
w: 6.123233995736766e-17,
}
==================
Isometry
==================
Normal
{ translation: [1, 2, 3], rotation: { angle: 3.141592653589793, axis: (0, 1, 0) } }
Debug
Isometry { rotation: Quaternion { x: 0.0, y: 1.0, z: 0.0, w: 6.123233995736766e-17 }, translation: [1.0, 2.0, 3.0] }
Debug alternate
Isometry {
rotation: Quaternion {
x: 0.0,
y: 1.0,
z: 0.0,
w: 6.123233995736766e-17,
},
translation: [
1.0,
2.0,
3.0,
],
}
==================
UnitComplex
==================
Normal
UnitComplex angle: 0.15
Debug
Complex { re: 0.9887710779360422, im: 0.14943813247359922 }
Debug alternate
Complex {
re: 0.9887710779360422,
im: 0.14943813247359922,
}
==================
Similarity
==================
Normal
{ translation: [1, 2, 3], rotation: { angle: 3.141592653589793, axis: (0, 1, 0) }, scaling: 0.1 }
Debug
Similarity { isometry: Isometry { rotation: Quaternion { x: 0.0, y: 1.0, z: 0.0, w: 6.123233995736766e-17 }, translation: [1.0, 2.0, 3.0] }, scaling: 0.1 }
Debug alternate
Similarity {
isometry: Isometry {
rotation: Quaternion {
x: 0.0,
y: 1.0,
z: 0.0,
w: 6.123233995736766e-17,
},
translation: [
1.0,
2.0,
3.0,
],
},
scaling: 0.1,
}
==================
Quaternion
==================
Normal
2 + 3i + 4j + 1k
Debug
Quaternion { x: 2.0, y: 3.0, z: 4.0, w: 1.0 }
Debug alternate
Quaternion {
x: 2.0,
y: 3.0,
z: 4.0,
w: 1.0,
}
==================
UnitDualQuaternion
==================
Normal
{ translation: [0, 0, 0], rotation: { angle: 3.141592653589793, axis: (0, 1, 0) } }
Debug
DualQuaternion { real: Quaternion { x: 0.0, y: 1.0, z: 0.0, w: 6.123233995736766e-17 }, dual: Quaternion { x: 0.0, y: 0.0, z: 0.0, w: 0.0 } }
Debug alternate
DualQuaternion {
real: Quaternion {
x: 0.0,
y: 1.0,
z: 0.0,
w: 6.123233995736766e-17,
},
dual: Quaternion {
x: 0.0,
y: 0.0,
z: 0.0,
w: 0.0,
},
}
I've just realised that this does mean that not everything will be perfectly aligned in debug alternate
I think this is fine. People who care can use padding/precision specifiers as usual.
New output
I still personally prefer the more concise Debug form for Quaternion had by delegating directly to the inner coords. If we don't feel it necessary to name the fields in Vector4, I'm not sure why we'd go out of our way to do so for Quaternion.
Just a few comments (have more to say but no time atm):
I do think alignment in alternate debug mode matters a lot for readability, but I think this can be punted to a separate PR. It's any way a huge improvement over the current (very broken!) state.
I still personally prefer the more concise Debug form for Quaternion had by delegating directly to the inner coords. If we don't feel it necessary to name the fields in Vector4, I'm not sure why we'd go out of our way to do so for Quaternion.
For a vector, the order of the components is given. A quaternion has multiple ways in which it can be represented as a vector, say, as [x, y, z, w] or [w, x, y, z]. So I think just 4 numbers for a quaternion is ultimately ambiguous. I would personally prefer this be made more explicit.
I think just 4 numbers for a quaternion is ultimately ambiguous
That's a fair point, although the documentation is clear. How about ([x, y, z], w)? Maybe that's too distant from construction...
My opinion here isn't super strong, this PR is a nice improvement as-is.
I think just 4 numbers for a quaternion is ultimately ambiguous
That's a fair point, although the documentation is clear. How about
([x, y, z], w)? Maybe that's too distant from construction...My opinion here isn't super strong, this PR is a nice improvement as-is.
Hmm, you're right that the docs are pretty clear on the storage order. I generally agree with you that conciseness is really important, given that during debugging you might be outputting complex structs or large amounts of data, and verbose output is a real hindrance to effective debugging in my experience. So I think I'd also be in favor of the more compact 4-number output for quaternions.
@owenbrooks: do you intend to continue working on this issue? I think your contribution here is very valuable and I think we were close to having converged. Would be nice to wrap it up if we can, although of course I understand if other things take priority for you.