I’ve been working through an idea for a package, and I’d love your feedback.
Tl;Dr: This package allows you to create method dispatches based on the methods defined for a type, not the place the type has in the type lattice.
Here’s a little snippet to get the idea:
@duck_type struct FiniteIterable{T}
# the signatures that must be defined to make a type a `FiniteIterable`
function Base.iterate(::This)::Union{Nothing, Tuple{T, <:Any}} end
function Base.iterate(::This, ::Any)::Union{Nothing, Tuple{T, <:Any}} end
function Base.length(::This)::Int end
# Calculate the paramater for `FiniteIterable` for a given type
@narrow InputT -> FiniteIterable{eltype(InputT )}
end
@duck_dispatch function my_collect(arg1::FiniteIterable{T}) where {T}
return T[x for x in arg1]
end
ch = Channel{Int}() do ch
for i in 1:2
put!(ch, i)
end
end
my_collect((1,2)) # [1,2]
my_collect([1,2]) # [1,2]
# When a type does not have the methods required, an error is thrown
# currently, this error is a generic ExceptionError, but will soon be a proper
# MethodError
my_collect(ch) # error -- no method `length`
In the background, it wraps any DuckType
args in a Guise{<:DuckType, T}
. When it gets to a method call listed in the DuckType
’s interface, it unwraps the data.
DuckType
’s can be composed together so you can build complex DuckType
s without cluttering the method table with tons of new methods. Composition looks like this:
@duck_type struct Indexible{T}
function getindex(::This, <:Any)::T end
@narrow InputT -> Indexible{eltype(InputT)}
end
@duck_type struct Container{T} <: Union{FiniteIterable{T}, Indexible{T}}
@narrow InputT -> Container{eltype(InputT )}
end
Now, a Container
has behaviors for iterate
, length
, and getindex
.
Status
All the dispatch work happens in the type domain, so it completely compiles away in my testing. Since a lot of the composition logic requires recursion, I don’t know how the inference performs in deeply nested DuckType
definitions. That said, I see this kind of flexibility as most useful at the top level of a script. I don’t think it makes sense to use this inside a hot loop in general.
I will soon add a @check
function along with @narrow
to allow custom checking of input types beyond just “does it have these methods.” This is because a lot of types have default methods that don’t make sense. For example, iterate
and eltype
are defined for Int
, but you probably want to exclude Int
from an Iterable
DuckType
.
All my error messages are generic and unhelpful at the moment. One of the perks of owning the dispatching infrastructure is that I can design very nice error messages. That’s on my roadmap.
Edit: After a couple rounds of optimization, there is no overhead for many types!