DuckDispatch.jl

If it quacks like a duck... dispatch on it!
Author mrufsvold
Popularity
15 Stars
Updated Last
5 Months Ago
Started In
May 2024

DuckDispatch.jl Duck Dispatch logo

Stable Dev Build Status Coverage Aqua

DuckDispatch.jl is an experimental package which attempts to make it easy to dispatch a method based on the behavior of a type, not its place in the type hierarchy. At a high-level, it allows the user to define a number of method signatures which constitute a DuckType. Then, any type which has an implementation for those methods can be wrapped in a Guise{D<:DuckType, T}. This Guise type is then hooked into the normal Julia dispatch machinery.

Why?

It often does not matter if an input is of a specific type (like a vector, a channel, or a set); it matters that it is has a certain functionality (like iterable). While creating a method with a completely generic argument will work, it also provides no guarantees that the input will have the necessary methods.

By dispatching on a DuckType, we get:

  1. compile time guarantees that there won't be any method errors for the Behaviors defined for the DuckType
  2. helpful errors for calls to a method that is not defined for the DuckType
  3. a method signature that is more informative about the meaning of is arguments

The Basics

To define an Iterable DuckType, we can do the following:

using DuckDispatch
@duck_type struct Iterable{T}
    function Base.iterate(::This)::Union{Nothing, Tuple{T, <:Any}} end
    function Base.iterate(::This, ::Any)::Union{Nothing, Tuple{T, <:Any}} end
    @narrow T -> Iterable{eltype(T)}
end

Now, we can create a new function that dispatches on this DuckType:

@duck_dispatch function my_collect(arg1::Iterable{T}) where {T}
    v = T[]
    for x in arg1
        push!(v, x)
    end
    return v
end

using Test
@test my_collect((1,2)) == [1,2]
@test my_collect(1:2) == [1,2]
@test my_collect((i for i in 1:2)) == [1,2]

Iterable is pretty limited without length. We can compose it with some new behaviors to build a more feature-rich DuckType!

@duck_type struct FiniteIterable{T} <: Union{Iterable{T}}
    function Base.length(::This)::Int end
    @narrow T -> FiniteIterable{eltype(T)}
end
@duck_dispatch function my_collect(arg1::FiniteIterable{T}) where {T}
    return T[x for x in arg1]
end

More Information

See the developer documentation for more information the internals of this package.