diff --git a/Project.toml b/Project.toml index af66b11..1ad358b 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "DispatchedTuples" uuid = "508c55e1-51b4-41fd-a5ca-7eb0327d070d" authors = ["Charles Kawczynski "] -version = "0.1.2" +version = "0.1.3" [compat] julia = "1.5" diff --git a/README.md b/README.md index ef8f37f..a7b5308 100644 --- a/README.md +++ b/README.md @@ -23,9 +23,23 @@ [bors-img]: https://bors.tech/images/badge_small.svg [bors-url]: https://app.bors.tech/repositories/32073 -DispatchedTuples.jl defines one user-facing type: `DispatchedTuple`, and one user-facing method: `dispatch`. A `DispatchedTuple` is similar to a compile-time dictionary, that uses dispatch for the look-up. +A `DispatchedTuple` is like a dictionary, except + + - the keys are **instances of types** + - they are backed by tuples, so they are GPU-friendly + - multiple keys are allowed (except for `DispatchedTupleSet`) + - each unique key returns a tuple of values given by that key (in order) + +All `AbstractDispatchedTuple`s take a `Tuple` of `Pair`s, where the `first` field of the `Pair` (the "key") is **an instance of the type you want to dispatch on**. The `second` field of the `Pair` is the quantity (the "value", which can be anything) returned by `dispatch(::AbstractDispatchedTuple, key)`, the one user-facing method exported by DispatchedTuples.jl. + +DispatchedTuples.jl has several user-facing types: + + - `DispatchedTuple` - a dispatched tuple (example below) + + - `DispatchedTupleSet` - a dispatched tuple set-- duplicate keys are not allowed. An error is thrown when `dispatch` is called with a duplicate key. + + - `DispatchedTupleDict` - a dispatched tuple dict-- duplicate keys are allowed, but `dispatch` returns value from the last non-unique key. -`DispatchedTuple` takes a `Tuple` of `Pair`s, where the `first` field of the `Pair` (the "key") is **an instance of the type you want to dispatch on**. The `second` field of the `Pair` is the quantity (the "value", which can be anything) returned by `dispatch`. ## Example diff --git a/docs/src/index.md b/docs/src/index.md index 75d3b4c..42c3a8c 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -48,5 +48,6 @@ For convenience, `DispatchedTuple` can alternatively take a `Tuple` of two-eleme DispatchedTuples.AbstractDispatchedTuple DispatchedTuples.DispatchedTuple DispatchedTuples.DispatchedTupleSet +DispatchedTuples.DispatchedTupleDict DispatchedTuples.dispatch ``` diff --git a/src/DispatchedTuples.jl b/src/DispatchedTuples.jl index 9b3af9c..0f891c7 100644 --- a/src/DispatchedTuples.jl +++ b/src/DispatchedTuples.jl @@ -2,7 +2,11 @@ module DispatchedTuples import Base -export AbstractDispatchedTuple, DispatchedTuple, DispatchedTupleSet, dispatch +export AbstractDispatchedTuple, dispatch + +export DispatchedTuple +export DispatchedTupleSet +export DispatchedTupleDict struct NoDefaults end @@ -14,6 +18,10 @@ dispatched tuples. """ abstract type AbstractDispatchedTuple{T <: Tuple, D} end +##### +##### DispatchedTuple +##### + """ DispatchedTuple(tup[, default_value]) @@ -49,8 +57,6 @@ struct DispatchedTuple{T,D} <: AbstractDispatchedTuple{T, D} end end -first_eltype(::Type{Tuple{T, V}}) where {T,V} = T - """ dispatch(::DispatchedTuple, type_instance) @@ -88,11 +94,9 @@ end expr end -Base.isempty(dt::AbstractDispatchedTuple) = Base.isempty(dt.tup) -Base.length(dt::AbstractDispatchedTuple) = Base.length(dt.tup) -Base.map(f, dt::AbstractDispatchedTuple) = Base.map(f, dt.tup) -Base.keys(dt::AbstractDispatchedTuple) = map(x->x[1], dt) -Base.values(dt::AbstractDispatchedTuple) = map(x->x[2], dt) +##### +##### DispatchedTupleSet +##### """ DispatchedTupleSet(tup[, default_value]) @@ -157,7 +161,78 @@ end end end +##### +##### DispatchedTupleDict +##### + +""" + DispatchedTupleDict(tup[, default_value]) + +Similar to `DispatchedTuple`, except: + - keys need not be unique, _but_ only the last key is used + - returns the value, and not a tuple of values. +""" +struct DispatchedTupleDict{T,D} <: AbstractDispatchedTuple{T, D} + tup::T + default::D + function DispatchedTupleDict(tup_in::T, default=NoDefaults()) where {T<:Tuple} + if eltype(tup_in) <: Pair + tup = map(x->(x.first, x.second), tup_in) + else + tup = tup_in + end + return new{typeof(tup), typeof(default)}(tup, default) + end +end + +""" + dispatch(::DispatchedTupleDict, type_instance) + +Dispatch on the [`DispatchedTupleDict`](@ref), based +on the instance of the input type `type_instance`. +""" +@generated function dispatch(dt::DispatchedTupleDict{TT, NoDefaults}, ::T) where {TT, T} + match_count = 0 + expr = quote end + for (i,k) in enumerate(fieldnames(TT)) + if first_eltype(fieldtype(TT, i)) == T + match_count += 1 + expr = :(dt.tup[$i][2]) + end + end + if match_count == 0 + push!(expr.args, :(throw(error("No method dispatch defined for type $T")))) + end + return expr +end + +@generated function dispatch(dt::DispatchedTupleDict{TT,D}, ::T) where {TT, D, T} + match_count = 0 + expr = quote end + for (i,k) in enumerate(fieldnames(TT)) + if first_eltype(fieldtype(TT, i)) == T + match_count += 1 + expr = :(dt.tup[$i][2]) + end + end + if match_count == 0 + return :(dt.default) + else + return expr + end +end + # Nested dispatch calls: dispatch(dt::AbstractDispatchedTuple, a, b...) = dispatch(dispatch(dt, a), b...) +# Interface / extending: +Base.isempty(dt::AbstractDispatchedTuple) = Base.isempty(dt.tup) +Base.length(dt::AbstractDispatchedTuple) = Base.length(dt.tup) +Base.map(f, dt::AbstractDispatchedTuple) = Base.map(f, dt.tup) +Base.keys(dt::AbstractDispatchedTuple) = map(x->x[1], dt) +Base.values(dt::AbstractDispatchedTuple) = map(x->x[2], dt) + +# Helper +first_eltype(::Type{Tuple{T, V}}) where {T,V} = T + end # module diff --git a/test/runtests.jl b/test/runtests.jl index 7a1ad73..665e221 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -54,6 +54,41 @@ end @test isempty(dt) == isempty(tup) end +##### +##### DispatchedTupleDict's +##### + +@testset "DispatchedTupleDict - base behavior" begin + dt = DispatchedTupleDict(((Foo(), 1), (Bar(), 2))) + @test dispatch(dt, Foo()) == 1 + @test dispatch(dt, Bar()) == 2 + @test_throws ErrorException dispatch(dt, FooBar()) + + dt = DispatchedTupleDict(((Foo(), 1), (Bar(), 2)), 0) + @test dispatch(dt, Foo()) == 1 + @test dispatch(dt, Bar()) == 2 + @test dispatch(dt, FooBar()) == dt.default +end + +@testset "DispatchedTupleDict - base behavior - Pair interface" begin + dt = DispatchedTupleDict((Pair(Foo(), 1), Pair(Bar(), 2))) + @test dispatch(dt, Foo()) == 1 + @test dispatch(dt, Bar()) == 2 + @test_throws ErrorException dispatch(dt, FooBar()) +end + +@testset "DispatchedTupleDict - multiple values, unique keys" begin + dt = DispatchedTupleDict(((Foo(), 1), (Bar(), 2), (Foo(), 3))) + @test dispatch(dt, Foo()) == 3 + @test dispatch(dt, Bar()) == 2 + @test_throws ErrorException dispatch(dt, FooBar()) + + dt = DispatchedTupleDict(((Foo(), 1), (Bar(), 2), (Foo(), 3)), 0) + @test dispatch(dt, Foo()) == 3 + @test dispatch(dt, Bar()) == 2 + @test dispatch(dt, FooBar()) == dt.default +end + ##### ##### DispatchedTupleSet's #####