-
Notifications
You must be signed in to change notification settings - Fork 21
Conversation
Codecov Report
@@ Coverage Diff @@
## master #187 +/- ##
==========================================
+ Coverage 64.56% 64.62% +0.05%
==========================================
Files 13 13
Lines 697 701 +4
==========================================
+ Hits 450 453 +3
- Misses 247 248 +1
Continue to review full report at Codecov.
|
Thanks, but I'd really like to avoid having to copy so much code from Base. This is JuliaLang/julia#2326. In the short term, I'm not sure what's the best approach. Isn't there a way to use the machinery from Base without reimplementing the full method? |
src/nullablevector.jl
Outdated
@@ -311,3 +311,80 @@ function Base.empty!(X::NullableVector) | |||
empty!(X.isnull) | |||
return X | |||
end | |||
|
|||
function Base.promote_rule{T1,T2}(::Type{T1}, ::Type{Nullable{T2}}) |
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.
This is not actually needed (and would have to live in Base anyway). Indeed you should never call promote_rule
directly, but use promote_type
instead. This already works:
julia> promote_type(Nullable{Int64}, Int64)
Nullable{Int64}
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.
Yes, you're right. I see now that promote_type
has the consistency I was expecting from promote_rule
. I'll need to learn more about those differences.
julia> promote_rule(Nullable{Int64}, Int64)
Nullable{Int64}
julia> promote_rule(Int64, Nullable{Int64})
Union{}
julia> promote_type(Nullable{Int64}, Int64)
Nullable{Int64}
julia> promote_type(Int64, Nullable{Int64})
Nullable{Int64}
I guess one partial solution is to define methods like |
src/nullablevector.jl
Outdated
promote_rule(Nullable{T2}, T1) | ||
end | ||
|
||
function Base.typed_hcat{T}(::Type{T}, A::AbstractVecOrMat...) |
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.
It's not OK to override Base methods like this with signatures that only involve Base types. That will affect unrelated packages and is known as type piracy.
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.
That makes perfect sense. Thanks for explaining what type piracy is
I need to figure out how to handle the 3+ argument case but this is working nicely for the 2 argument cases julia> @test typeof(vcat(1:2, NullableArray(3:4))) == NullableArrays.NullableArray{Int64,1}
Test Passed
Expression: typeof(vcat(1:2,NullableArray(3:4))) == NullableArrays.NullableArray{Int64,1}
Evaluated: NullableArrays.NullableArray{Int64,1} == NullableArrays.NullableArray{Int64,1}
julia> @test typeof(vcat([1 2], NullableArray([3 4]))) == NullableArrays.NullableArray{Int64,2}
Test Passed
Expression: typeof(vcat([1 2],NullableArray([3 4]))) == NullableArrays.NullableArray{Int64,2}
Evaluated: NullableArrays.NullableArray{Int64,2} == NullableArrays.NullableArray{Int64,2}
julia> @test typeof(hcat(1:2, NullableArray(3:4))) == NullableArrays.NullableArray{Int64,2}
Test Passed
Expression: typeof(hcat(1:2,NullableArray(3:4))) == NullableArrays.NullableArray{Int64,2}
Evaluated: NullableArrays.NullableArray{Int64,2} == NullableArrays.NullableArray{Int64,2}
julia> @test typeof(hcat([1 2], NullableArray([3 4]))) == NullableArrays.NullableArray{Int64,2}
Test Passed
Expression: typeof(hcat([1 2],NullableArray([3 4]))) == NullableArrays.NullableArray{Int64,2}
Evaluated: NullableArrays.NullableArray{Int64,2} == NullableArrays.NullableArray{Int64,2} |
src/nullablevector.jl
Outdated
@@ -311,3 +311,14 @@ function Base.empty!(X::NullableVector) | |||
empty!(X.isnull) | |||
return X | |||
end | |||
|
|||
notnullarray = Union{filter!(x -> !isa(x, Type{NullableArray}), subtypes(AbstractArray))...} |
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 shouldn't need this. Just AbstractArray
should be enough, and the most specific method will take precedence.
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.
When usingAbstractArray
there's no way to exit the vcat -> vcat
loop and it results in StackOverflow. We either eternally prepend empty NullableArrays (vcat(NullableVector{eltype(A)}(), A, B)
) or eternally try to convert A to a NullableArray
(vcat(NullableArray(A), B)
)
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 now realize that if I call typed_hcat
, typed_vcat
instead of calling hcat/vcat
I can probably avoid the StackOverflow while using the simpler AbstractArray
as you suggested.
src/nullablevector.jl
Outdated
notnullmatrix = typeintersect(notnullarray, AbstractMatrix) | ||
notnullvector = typeintersect(notnullarray, AbstractVector) | ||
|
||
Base.vcat{T <: notnullarray}(A::T, B::NullableArray) = Base.vcat(NullableArray(A), B) |
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.
This is wasteful since it creates a copy. Maybe we can use this trick to force creating a NullableArray
: vcat(NullableArray{eltype(A)}(), A, B)
.
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.
Also, no need for Base.
.
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 think this case of adding an empty NullableArray to the beginning of the vcat/hcat
call only works on vcat
with Vector
s, but I've added it for that case. Every other case I tried will fail in dimension mismatch checks
src/operators.jl
Outdated
@@ -1,7 +1,7 @@ | |||
## Lifted operators | |||
|
|||
importall Base.Operators | |||
import Base: promote_op, abs, abs2, sqrt, cbrt, scalarmin, scalarmax, isless | |||
import Base: abs, abs2, cbrt, isless, promote_op, scalarmin, scalarmax, sqrt |
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.
Unrelated.
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.
reverted
test/nullablevector.jl
Outdated
@@ -223,4 +223,21 @@ module TestNullableVector | |||
X = NullableArray(A, M) | |||
empty!(X) | |||
@test isempty(X) | |||
|
|||
@test typeof(hcat(NullableArray(1:2), 3:4)) == NullableArrays.NullableArray{Int64,2} |
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.
No need for NullableArrays.
.
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.
removed
src/nullablevector.jl
Outdated
@@ -1,3 +1,5 @@ | |||
import Base: hcat, vcat |
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.
This file uses Base.
to prefix the function extensions. Should I add all of the functions to this import call? It's an unrelated change but importing these two and leaving everything else to be Base.
is inconsistent
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.
Yeah, better not import anything and use the Base.
prefix when overloading methods.
Should these promote to NullableCategoricalArray? julia> vcat(categorical([1,2,3]), NullableArray([4,5,6]))
6-element NullableArrays.NullableArray{Int64,1}:
1
2
3
4
5
6
julia> vcat(NullableArray([4,5,6]), categorical([1,2,3]))
6-element NullableArrays.NullableArray{Int64,1}:
4
5
6
1
2
3 |
src/nullablevector.jl
Outdated
@@ -311,3 +313,27 @@ function Base.empty!(X::NullableVector) | |||
empty!(X.isnull) | |||
return X | |||
end | |||
|
|||
function vcat(A::AbstractVector, B::AbstractVector...) |
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.
This is still type piracy. There needs to be NullableArray
in the signature. Currently I don't think this can be fixed in full generality. We can include a few methods for all possible combinations up to e.g. 3 arguments, i.e:
::NullableArray, ::AbstractArray
::AbstractArray, ::NullableArray
::NullableArray, ::AbstractArray, ::AbstractArray
::AbstractArray, ::NullableArray, ::AbstractArray
::AbstractArray, ::AbstractArray, ::NullableArray
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 think defining these functions would be a regression compared to just leaving the current functionality as is.
# this rule seems more intuitive
isa(array[1], NullableArray) ? NullableArray{T} : Array[Nullable{T}]
# than this rule
isa(array[1], NullableArray) ||
isa(array[2], NullableArray) ||
isa(array[3], NullableArray) ? NullableArray{T} : Array[Nullable{T}]
If the general functionality is type piracy then I feel the best way forward would be to either address JuliaLang/julia#2326 directly or accept the current behavior. Thoughts?
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 know. Clearly we can't reach a satisfying state without improvements in Julia. But which of the partial solutions is better, I don't know...
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.
Similar sentiments here. I'll implement the 2 argument cases for hcat/vcat
and now at least we have everything written up and documented.
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.
If you implement the two-argument versions, please also implement the three-argument versions. Else the rule is going to be even more complex than what you wrote above.
Of course, this rests on the assumption that concatenating more than 3 arrays is a somewhat rare operation, so that it's not a big issue if other variants behave differently. Might not be the case, but only special-casing two-argument methods is clearly a worse solution.
Yes, but that's tricky since doing so forces one package to depend on the other one, and as long as StatsModels depends on CategoricalArrays we'd better get rid of the NullableArrays dependency (JuliaData/CategoricalArrays.jl#55). So for now I would leave this case as is. |
src/nullablevector.jl
Outdated
typed_vcat(promote_eltype(A, B...), NullableArray(A), B...) : | ||
typed_vcat(promote_eltype(A, B...), A, B...) | ||
function vcat(A::AbstractVector, B::NullableVector) | ||
typed_vcat(promote_eltype(A, B), NullableArray(A), B) |
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.
Please reduce the number of spaces used for indentation here by 50%
I think we should close this. I read through a few dozen pages of search results for |
I've tried starting a discussion about the way to move forward at JuliaLang/julia#20815, let's see what can be done about this. |
In the meantime, thanks so much for your hard work here, Cameron. Much appreciated. |
#167