-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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
Falling back to Base.convert during construction does not work for custom types with a single field #15120
Comments
Maybe related #15044 ? |
It fails on Julia 0.4.3. |
The wording in the manual is not ideal. Defining |
Why does the type constructor not fall back to the |
Because of the default constructors:
|
I apologize if I am being dense. But if this is the intended behavior I do not understand the reasoning behind it. Especially since the following example works. type C
v::Float64
w::Float64
end
type D
v::Float64
w::Float64
end
Base.convert(::Type{C}, d::D) = C(d.v/2, d.w/2)
d = D(2, 2)
C(d) |
This was surprising to me too. I would have assumed that the default constructor for In contrast, the second case with types |
Just to clarify on that second case, the following will give the same error: type C
v::Float64
w::Float64
end
type D
v::Float64
w::Float64
end
Base.convert(::Type{D}, c1::C, c2::C) = D(c1.v, c2.w)
c1 = C(2, 2)
c2 = C(3, 3)
D(c1, c2) |
@swt30 Thanks for the explanation. I finally got it. I have opened a PR for clarifying the documentation. This corner case seems very unfortunate to me, though. "You can also extend Base.convert unless the number of arguments matches the number of fields" might be hard to sell to a newcomer. |
@helgee I agree that it's not pretty, but maybe it's better to frame it the way Jeff did? "If you want to use type B
v::Float64
end
type A
v::Float64
Base.convert(::Type{A}, b::B) = new(b.v/2)
end
b = B(2)
A(b) |
@cstjean In my real-world code the whole thing is not an issue because the number of arguments and the number of fields are not the same. I was just stumped by it during testing. In cases were conversion and construction are not the same it is also easy to circumvent the problem by doing this: type A
v::Float64
end
type B
v::Float64
end
Base.convert(::Type{A}, b::B) = A(b.v/2)
# Explicit constructor
A(b::B) = convert(A, b)
b = B(2)
A(b) I am just bothered by it because it seems like a serious WAT?! to me and the manual was actively misleading. |
I agree this is terribly confusing. Would it be possible to stop defining the default |
@helgee, to further complicate things, I'm not sure that A(b::B) = convert(A, b) is exactly right either. It's what I do, but Base.call(::Type{A}, b::B) = convert(A, b) or I guess on master something like (::Type{A})(b::B) = convert(A, b) I don't know what the advantage of this is as compared to writing |
We didn't always generate default constructors for A case can be made that types should have to individually opt-in to the fall-back-to-convert behavior. That way the default would be to keep convert and construct totally separate functions. It also fits better with the new functions design. Of course this would be a significant breaking change. |
Another strategy would be to get rid of the distinction between |
I don't think that's workable; for example |
I find JeffB's note "...the default would be to keep convert and construct totally separate functions [and that would fit] better with the new functions design." compelling as it better smooths future Julia. And having the sort of disclarity discussed above complicate proper use of whatever is to serve as a protocol or as refinable interface specifier is best avoided. |
+1. Furthermore, I believe that if the convert-constructor was generated by default for any bitstype then this would fit better with expectations (and not be a significantly breaking change). This would be more similar to how default constructors are created for other types, and I feel this may be more consistent with the primary purpose and usage of bitstypes – they are pointer-free bitstypes, which seems like it would make them mostly useless as containers – but generally meaningful for conversion. And besides: that should get rid of the last method override warning during bootstrapping. |
Agreed. Let's make |
We should do this sooner rather than later and tear the bandaid off. |
If we make |
I started digging into this, and it is a bit tricky. So far the change that seems to be the easiest and work the best is to restrict the existing definition to subtypes of
There is a bit of precedent for defining certain things for |
Here's some more detail. @vtjnash & I are not sure the bitstype approach makes sense. There are lots of numeric types defined as structs, so it won't buy us much backwards compatibility. It also means definitions like the following exist in
Since that's in |
We had talked about merging |
See above. There is also Core.Inference.convert, and it wants to add many of the same definitions that Base does. I also don't know if it makes sense to tie this to bitstypes. Ever since |
Could we maybe make a trait to determine when this happens? |
Getting too late in the process to make more large breaking changes, but worth revisiting in the future to see if there's a better option for resolving the confusing behavior. |
To chime in regarding a case where However, using |
@vtjnash and I had a good discussion about this recently, and we decided that we might have simply gotten this backwards. The idea is that essentially every type has a constructor, with possibly varying semantics, but
The current definition I think a major reason we implemented the current approach is that we have tons of |
Doesn't the idea that |
That's one of the major reasons we did it this way --- |
I run into this with every new package I sketch. My approach now is to define the type, some Base.convert pairings |
When I run the following code
it fails with this error message because the implicit constructor is called.
I expected this to work since the manual states:
The following code on the other hand works:
It seems that the new constructor is only generated for built-in types and not for custom types.
EDIT: Typo.
The text was updated successfully, but these errors were encountered: