-
-
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
Method inheritance #30408
Comments
And in fact, a lot of previously defined traits could disappear. HasLength, HasSize, HasEltype, etc. |
After thinking about this for an hour I don't think this would be useful. For example, if you defined:
|
Ok, here's a better system that I think is actually workable. Instead of relying on traits like HasEltype, instead use:
and then instead of dispatching based on AbstractArray, do
|
Ok so this is actually kinda a cool idea right? |
Perhaps? I'm a little unclear on what you're proposing / requesting here. |
Instead of relying on duck-typing, we could do compile-tile math to check for the existence of required methods. |
Does that makes sense? Or am I crazy |
I felt the need for a similar feature when I was writing my own ReinterpretArray. |
So here's a more detailed example: to_type(x) = typeof(x)
to_type(x::Type) = x
"""
works(f_type::Type, in_types::Type...)
Check whether a function of type `f_type` is expected to work on arguments of types `in_types`.
works(f, ins...) = works(typeof(f), typeof.(ins)...)
Provided for convenience; define new `works` methods `Type` arguments.
"""
works(ins...) = works(to_type.(ins)...)
works(::Vararg{Type}) = false
works(::Type{F}, ::Type{A}) where {F <: typeof(size), A <: AbstractArray} = true
works(::Type{F}, ::Type{A}, ::Vararg{Type{Int}, N}) where {N, F <: typeof(getindex), A <: AbstractArray{T, N} where {T}} = true
works(::Type{F}, ::Type{A}, ::Vararg{Type{Int}, N}) where {N, F <: typeof(setindex!), A <: AbstractArray{T, N} where {T}} = true
using Test: @test
@test works(size, [1])
@test works(getindex, [1], 1)
@test works(setindex!, [1], 1)
has_linear_indices(x) = works(size, x) & works(getindex, x, 1) & works(setindex!, x, 1)
@test has_linear_indices([1])
@code_warntype has_linear_indices([1])
# return true What do people think? |
@StefanKarpinski does that clear things up? |
Doesn't |
Sort of, but |
This issue is titled "Required method" and continues with "If these were inferable, defining interfaces could be quite a bit robust." I still have no idea what this means. What is a "required method"? Is this a language feature you are proposing adding to the language? Are you proposing a generic API for asking what a type expects people to implement? What does it have to do with inference and constant propagation? That seems totally unrelated... |
:( I’m always so sad when things make perfect sense in my head but I can’t communicate effectively. Let me try again? Say you have something like mappedarray. Its not an abstract array because it doesn’t have an eltype, and you can’t setindex!. But you can get its size and getindex into it. So you have a function (like sum), which would theoretically work on both abstractarrays and mapped arrays. If you don’t want to have to write the same method twice, you could use works to check whether or not an input supports getindex. |
How does this improve on traits? Hmm, maybe I should add a GSOC project: make Traitor work. I'm pretty sure we have everything we need now, see andyferris/Traitor.jl#8. |
How would one declare this sort of thing? Using method definitions like in your post above? |
I mean, I guess I'm basically talking about traits, but I'm being a bit more specific by organizing them around methods, and you can combine them in various logical ways because the collect(it) =
if works(size, it)
if works(eltype, it)
collect_size_eltype(it)
else
collect_size_no_eltype(it)
end
elseif works(length, it)
if works(eltype, it)
collect_length_eltype(it)
else
collect_length_no_eltype(it)
end
else
if works(eltype, it)
collect_no_length_eltype(it)
else
collect_no_length_no_eltype(it)
end
end |
Compare with traits: @traitor collect(it::::Tuple{HasShape,HasEltype}) = collect_size_eltype(it)
@traitor collect(it::::Tuple{HasShape,EltypeUnknown}) = collect_size_no_eltype(it)
@traitor collect(it::::Tuple{HasLength,HasEltype}) = collect_length_eltype(it)
@traitor collect(it::::Tuple{HasLength,EltypeUnknown}) = collect_length_no_eltype(it)
@traitor collect(it::::Tuple{SizeUnknown,HasEltype}) = collect_no_length_eltype(it)
@traitor collect(it::::Tuple{SizeUnknown,EltypeUnknown}) = collect_no_length_no_eltype(it) How different is that, really? I agree that not needing to know the trait name has a certain appeal; OTOH, your definition is "closed" whereas someone can still specialize this further. |
Eh I guess that's the same with nicer syntax. Well except that you could do any operation that works with Bools (|, xor, any, etc.) if traits were based on them. |
Further thoughts in light of #34196: maybe a broader idea is a system of method inheritance. A method might only be defined on a type if other methods are also defined on that type. |
Closed in favor of #35095 |
If these were inferable, defining interfaces could be quite a bit robust. For example, you could define
is_abstract_array(x) = hasmethod(eltype, Tuple{typeof(x)}) && hasmethod(getindex, Tuple{typeof(x), Int})
.Edit: constant propagatable hasproperty would also be cool
The text was updated successfully, but these errors were encountered: