Skip to content
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

Inconsistent error types for literal powers #24699

Open
thofma opened this issue Nov 22, 2017 · 8 comments
Open

Inconsistent error types for literal powers #24699

thofma opened this issue Nov 22, 2017 · 8 comments
Labels
domain:error handling Handling of exceptions by Julia or the user

Comments

@thofma
Copy link
Contributor

thofma commented Nov 22, 2017

Due to #24240, which for numerical reasons has defined a catchall Base.literal_pow(::typeof(^), x, ::Type{Val{p}}) for all types, there are now inconsistent error types:

julia> "a"^-1
ERROR: MethodError: no method matching inv(::String)
Closest candidates are:
  inv(::Complex{Float64}) at complex.jl:391
  inv(::Integer) at int.jl:46
  inv(::Complex{#s55} where #s55<:Integer) at complex.jl:258
  ...
Stacktrace:
 [1] @generated body at ./none:0 [inlined]
 [2] literal_pow(::typeof(^), ::String, ::Val{-1}) at ./<missing>:0
 [3] top-level scope

julia> "a"^Int(-1)
ERROR: ArgumentError: can't repeat a string -1 times
Stacktrace:
 [1] repeat(::String, ::Int64) at ./strings/string.jl:488
 [2] ^(::String, ::Int64) at ./strings/types.jl:204
 [3] top-level scope

This is obviously bad.

The same problem happens if a user defines for his type T a function

^(::T, ::Int)

and is later suprised that T()^-1 throws a MethodError, although he defined it for integer exponents. This is further complicated due to #21014, which makes it impossible for a beginner to find out what is actually happening.

Defining inv(::T) is not an option (it does not make any sense in general). Not every object for which you use ^ is a number.

What about making the fancy literal_pow functions only apply to Base or number types? Together with the catchall

Base.literal_pow(::typeof(^), x, ::Type{Val{p}}) = x^p

this would solve the problem.

@thofma thofma changed the title Inconsistent error messages for literal powers Inconsistent error types for literal powers Nov 22, 2017
@stevengj
Copy link
Member

stevengj commented Nov 22, 2017

If you want x^-1 to throw a DomainError, I would think it is reasonable to want inv(x) to throw it too.

Note that there are lots of non-number types in which you want x^-n to work, e.g. Matrix{Int}. I think that having one exception rather than another exception is a small price to pay for having this fallback.

Note also that if your type defines * (for x^+n), then probably it has a multiplicative identity (one), so it has at least one value for which inv is defined.

@thofma
Copy link
Contributor Author

thofma commented Nov 22, 2017

Why must there be *? Maybe I just like the exponential notation for my custom types but they are not related to any binary operation at all. I don't think it is a good idea to assume that anything that overloads ^ must be some object for which * or inv makes sense. While I know that julia aims at providing a tool for numerical computations, there is no reason to block its usage as a general programming language by imposing arbitrary restriction on user defined types. (Here the restriction is: if you do ^ you must do inv).

The point is that there was no reason to define inv before. Everything just worked by defining ^(::T, ::Int). But all of a sudden one is confronted with obscure error messages when using literal powers.

(The point about inconsistent error types for Base types still stands.)

@stevengj
Copy link
Member

stevengj commented Nov 22, 2017

Sure, you can overload ^n to send an email n times or whatever, but in general we discourage this kind of punning in Julia, and I'm not convinced it's worth the loss of functionality to avoid slightly obscure error messages in such cases. You can do as much general-purpose programming as you want — overloading ^ or * with non-algebraic meanings is not necessary.

@pabloferz
Copy link
Contributor

Would you really loose the functionality is you only make the inv transformation for types in base leaving Base.literal_pow(::typeof(^), x, ::Val{p}) = x^p as a fallback? I mean we can always explain in the docs that the transformation occurs for some Base's types and that anyone could benefit from it by overloading Base.literal_pow(::typeof(^), ::SomeType, ::Val{-1})

@stevengj
Copy link
Member

(You need to write a @generated literal_pow method, like in Base, to make it work for all negative integer powers, not just -1.)

If by "Base types" you mean Number and AbstractMatrix, as opposed to concrete types, I agree that most of the functionality gains will remain.

@pabloferz
Copy link
Contributor

pabloferz commented Nov 23, 2017

Yeah I was referring to types like Number and AbstractMatrix, for which I agree with your argument of discouraging overloading them for doing "non-standard" operations.

I don't know if that would be good enough for @thofma who works with Nemo and Hecke (I have no idea if the types for which he wants the old behavior are or not embedded into the Number or AbstractMatrix type hierarchy.

Re. literal_pow, you're right, I see now that you need a @generated method.

@vtjnash
Copy link
Sponsor Member

vtjnash commented Nov 23, 2017

Re. literal_pow, you're right, I see now that you need a @generated method.

Only for a few days longer. Once CI is happy enough that we can merge stuff again, #24362 will make it unnecessary and I'll go back through and delete the generated annotations.

@thofma
Copy link
Contributor Author

thofma commented Nov 24, 2017

With "Base types" I was mainly thinking of Number and AbstractMatrix.

I don't think the error messages are "slightly obscure". In combination with #21014 and missing documentation it is not clear what is going on and why it is failing.

Imagine that as a user you just defined ^(::T, ::Int) and now T()^-1 is failing with something about inv and @generated code. Trying @which to see what is happening will confuse you even more, since @which T()^-1 points to ^(::T, ::Int). Great.

Here is another argument against the catchall Base.literal_pow. Assume that a user defined type T <: AbstractMatrix implements inv and ^(::T, ::Int). But maybe because of the nature of T, ^(::T, ::Int) is highly non-trivial for negative exponents. But then he is totally wrong if he thinks that T()^-1 or T()^-2 would call his powering function. (Think of types where the current behavior means twice as many allocations, like BigFloat just "bigger".)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
domain:error handling Handling of exceptions by Julia or the user
Projects
None yet
Development

No branches or pull requests

5 participants