-
Notifications
You must be signed in to change notification settings - Fork 8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Handling of AbstractIrrational
#50
Conversation
…which plots number symbols right-aligned
Codecov Report
@@ Coverage Diff @@
## master #50 +/- ##
=========================================
- Coverage 96.52% 96.12% -0.4%
=========================================
Files 6 6
Lines 489 516 +27
=========================================
+ Hits 472 496 +24
- Misses 17 20 +3
Continue to review full report at Codecov.
|
I agree that Irrationals should be handled, however, I don't believe that there should be a type for formatting displays of irrational. |
Thanks for your comments. I understand that introducing a new character is a bit strange. It also works for PhysicalConstants 😄 |
Just to give a small code example for PhysicalConstants:
|
Just a thought: It might be a good idea to introduce 'S' for symbols instead of 'v' for variables. Alternatively, one could also opt for 'N' for |
By the way, rational numbers come for free (and are also part of the tests), as they are Numbers |
I still don't believe that it's a good idea to add any characters, for the format strings that are meant to be C or Python compatible. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you need the Union{}
here?
I don't think that the dict should try use a union as the key.
The |
I am ok with removing 'S'. Do you have a good idea for resetting right-aligned strings? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you remove the addition of S
, the Union to the Dict, the separate handling of Complex?
You can add just Number
and Complex
as 's'
.
@@ -29,7 +29,10 @@ default_spec!(::Type{T}, ::Type{K}) where {T,K} = | |||
(DEFAULT_FORMATTERS[T] = DEFAULT_FORMATTERS[K]; nothing) | |||
|
|||
# seed it with some basic default formatters | |||
for (t, c) in [(Integer,'d'), (AbstractFloat,'f'), (AbstractChar,'c'), (AbstractString,'s')] | |||
ComplexInteger = Complex{T} where T<:Integer |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you want a subtype here? Why not just all anything of type Complex?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wanted automatic Integer
formatting for all Complex{Integer}
types in one go, that's why I changed the dict type tp Union{DataType,UnionAll}.
So I can write fmt_default!(Complex{T} where T<:Integer}
and also default_spec!(Complex{T} where T<:Integer}, 'f')
Otherwise we would need to to this for all subtypes individually.
The same hold for any other package that has a similar type system without an abstract supertype.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What this means is that you have to set up defaults for pretty much any arbitrary Complex
number, which I don't think is desirable behavior.
src/fmt.jl
Outdated
ComplexInteger = Complex{T} where T<:Integer | ||
ComplexFloat = Complex{T} where T<:AbstractFloat | ||
for (t, c) in [(Integer,'d'), (AbstractFloat,'f'), (AbstractChar,'c'), (AbstractString,'s'), | ||
(ComplexInteger, 'd'), (ComplexFloat, 'f'), (Number,'S'), (AbstractIrrational,'S')] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't need both Number
and AbstractIrrational
, since AbstractIrrational
is a subtype of Number
.
You could simply add (Number, 's')
. For complex, just add (Complex, 's')
, and have a special _srepr
function for any complex number.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I introduced both because I wanted to allow the user to set different default formats. E.g. in many cases users will want to display a floating point number for AbstrqactIrrational
s.
This can be done by calling fmt_default!(AbstractIrrational, 'f')
. Other Number types won't be affected and still display as strings.
default_spec(::Type{<:AbstractFloat}) = DEFAULT_FORMATTERS[AbstractFloat] | ||
default_spec(::Type{<:AbstractString}) = DEFAULT_FORMATTERS[AbstractString] | ||
default_spec(::Type{<:AbstractChar}) = DEFAULT_FORMATTERS[AbstractChar] | ||
default_spec(::Type{<:AbstractIrrational}) = DEFAULT_FORMATTERS[AbstractIrrational] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only the one for Number
is needed.
@@ -9,25 +9,26 @@ | |||
# width ::= <integer> | |||
# prec ::= <integer> | |||
# type ::= 'b' | 'c' | 'd' | 'e' | 'E' | 'f' | 'F' | 'g' | 'G' | | |||
# 'n' | 'o' | 'x' | 'X' | 's' | |||
# 'n' | 'o' | 'x' | 'X' | 's' | 'S' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really don't want any non-standard characters added, 's'
works fine for outputting symbols already.
|
||
function printfmt(io::IO, fs::FormatSpec, x) | ||
cls = fs.cls | ||
ty = fs.typ | ||
if cls == 'i' | ||
ix = Integer(x) | ||
ix = x |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You've introduced type instability here, and it's better to just get an exception if the format class isn't valid for the given type.
ty == 'd' || ty == 'n' ? _pfmt_i(io, fs, ix, _Dec()) : | ||
ty == 'x' ? _pfmt_i(io, fs, ix, _Hex()) : | ||
ty == 'X' ? _pfmt_i(io, fs, ix, _HEX()) : | ||
ty == 'o' ? _pfmt_i(io, fs, ix, _Oct()) : | ||
_pfmt_i(io, fs, ix, _Bin()) | ||
elseif cls == 'f' | ||
fx = float(x) | ||
fx = x |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above, better for this to get an exception, instead of slowing this down by introducing type instability, and covering up what might be useful information for the programmer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not so sure whether we should care about type stability here. (I am definitely not an expert here!)
But packages like PhysicalConstants
redefine float()
to work on unitful quantities. So float(x)
ends up to be something else than a subtype of AbstractFloat, e.g. float(c_0)
is 2.99792458e8 m s^-1
.
So if we keep fx = float(x)
, fx
will be type unstable anyhow.
Well, I think I found a way. I am still not so familiar with Types...
still struggling to formulate something similar likeComplex{<:Integer}
if I remove theUnion{}
Why do you need to separate out different types of complex numbers for defaults?
For things like complex numbers, you'll need to be careful about how width / precision is handled,
so that it doesn't just get treated like a string after the initial conversion to a string (which it looks like your code might be doing), which might end up having critical information truncated.
I think, you misinterpreted my code. I have added new less specific methods, e.g. _pfmt_f(io, x::Number, ...)
, which are called if the currently existing method does not cover the type, i.e. if x
is not a subtype of AbstractFloat
in case of _pfmt_f()
. The method calls the routine _pfmt_Number_f()
which then calls fmt_Number()
. (This method needs to be defined by the user, if he wants to add a new number type. For Complex
types I have already defined it.)
The last argument of that method is an anonymous formatting routine for the specific type ('i', 'e' or 'f') where the width of the FormatSpec
is set to -1
. This should be mainly a copy of the Number's show()
method where the value is replaced by f(value)
. The result type of the function needs to be a String
which is then passed to _pfmt_s()
with the original FormatSpec
, which then takes into account the width of the resulting string.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The result type of the function needs to be a String which is then passed to _pfmt_s() with the original FormatSpec, which then takes into account the width of the resulting string.
That's the problem, because depending on the length, it may truncate important information.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't see this: f"\{1.3f}(1+2im)"
results in "1.000 + 2.000im"
_pfmt_s()
only sets the minimal width.
But then we can't set defaults for all Rationals like I did in the test: `fmt_default!(Rational, 'f', prec = 2)` ...?
Also enhancing the scope for other types when no abstract type is available is less comfortable...
Edit: removed history from mail answer
|
Well, I think I found a way. I am still not so familiar with Types...
Edit:
- removed history from mail answer
- still struggling to formulate something similar like `Complex{<:Integer}` if I remove the `Union{}`
|
Why do you need to separate out different types of complex numbers for defaults? |
@@ -29,7 +29,10 @@ default_spec!(::Type{T}, ::Type{K}) where {T,K} = | |||
(DEFAULT_FORMATTERS[T] = DEFAULT_FORMATTERS[K]; nothing) | |||
|
|||
# seed it with some basic default formatters | |||
for (t, c) in [(Integer,'d'), (AbstractFloat,'f'), (AbstractChar,'c'), (AbstractString,'s')] | |||
ComplexInteger = Complex{T} where T<:Integer |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What this means is that you have to set up defaults for pretty much any arbitrary Complex
number, which I don't think is desirable behavior.
I think, it is strongly desirable, to have a common format for all |
I've added a PR #51 to address some of your issues, without adding new format characters. I'd like to collaborate with you, to make sure that the functionality you want is added to What things still need to be done, in your opinion, once PR #51 is merged? |
One issue for formatted display of numbers like complex or the ones with units, is how the width and precision values should be used. For strings, the precision gives the maximum length, which should not be used in that fashion for numbers. The width needs to apply to the entire string, not each number separately (as in a Complex number) |
Pleasure to work with you! Your PR covers a lot of things I wouldn't have written that fast.
|
I will close this PR and build a new one on top of #51 |
This is why I was explaining that there are different formatting methods, which are inconsistent, For example:
The Python style formatters ignore precision for strings, unlike the C style ones, i.e.:
|
O wow that is, indeed, an important piece of information that I was missing. That makes things more complicated. |
Currently
Format.jl
fails to handle the printout ofIrrationals
such aspi
.This can be circumvented by defining a new
DEFAULTFORMATTER
forAbstractIrrational
.I wondered, what a good standard print format might be
'f'
)In this PR I propose to introduce a new type and class character 'v' for "variable" which is per default right-aligned, so that the result of
pyfmt(FormatSpec("2v"), pi)
(EDIT: as well asfmt(pi,2)
) is" π"
Together with the
StringLiterals.jl
package this would make it possible to writef"\%(pi, 10)"
to print a right-aligned symbol.I understand that the introduction of a new character type breaks with the standard Python format, so I am a bit unsure, whether this is a good idea, or how it could be done better. So I'd be happy to receive some feedback here.
EDIT: two more comments ...
At the REPL
print(pi)
results inπ
, whereas print(1pi) results in3.141592653589793
. This behaviour would then be identical.