Skip to content

Commit

Permalink
Merge #23
Browse files Browse the repository at this point in the history
23: Ensure DispatchedSet unique keys on construction r=charleskawczynski a=charleskawczynski

This PR changes the behavior so that we error on the construction of `DispatchedSet`s, rather than throwing an error on `getindex(::DispatchedSet, key)` for non-unique keys. I think I prefer this behavior, erroring on construction.

At first I tried using `unique_elems`, but this incurred runtime overhead. Then, I tried the generated function `elems_are_unique`, and that seems to keep things all at compile time 🚀!

I think it's still useful for users to have `unique_elems` / `unique_keys(dt::AbstractDispatchedTuple)`, in case they may want to convert a `DispatchedTuple` into a `DispatchedSet`. I'll avoid exporting for now so that we can bikeshed the name / design.

Co-authored-by: Charles Kawczynski <[email protected]>
  • Loading branch information
bors[bot] and charleskawczynski authored Mar 18, 2021
2 parents b8457c3 + 87a36fb commit c4de321
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 13 deletions.
45 changes: 41 additions & 4 deletions src/DispatchedTuples.jl
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ function match_expr_val(dt, TT, T)
return expr, match_count
end

@generated function throw_key_error(::T) where {T}
:(throw(error("Non-unique keys given to DispatchedSet for $T")))
end

#####
##### DispatchedTuple
#####
Expand Down Expand Up @@ -137,13 +141,30 @@ struct DispatchedSet{T,D} <: AbstractDispatchedTuple{T, D}
default::D
function DispatchedSet(tup_in::T, default=NoDefaults()) where {T<:Tuple}
tup = unwrap_pair(tup_in)
_keys = map(t -> t[1], tup)
elems_are_unique(_keys) || throw_key_error(_keys)
return new{typeof(tup), typeof(default)}(tup, default)
end
end

# Accept vararg Pairs:
DispatchedSet(p...; default=NoDefaults()) = DispatchedSet(Tuple(p), default)

@generated function elems_are_unique(::T) where {T <: Tuple}
no_dupes = true
for i in 1:length(fieldnames(T))
T_i = fieldtype(T, i)
for j in i:length(fieldnames(T))
i == j && continue
T_j = fieldtype(T, j)
if T_i == T_j
no_dupes = false
end
end
end
return :($no_dupes)
end

"""
dispatch(::DispatchedSet, type_instance)
Expand All @@ -154,8 +175,6 @@ on the instance of the input type `type_instance`.
expr, match_count = match_expr_val(dt, TT, T)
if match_count == 0
return :(throw(error("No method dispatch defined for type $T")))
elseif match_count > 1
return :(throw(error("DispatchedSet has non-unique keys for type $T")))
else
return expr
end
Expand All @@ -165,8 +184,6 @@ end
expr, match_count = match_expr_val(dt, TT, T)
if match_count == 0
return :(dt.default)
elseif match_count > 1
return :(throw(error("DispatchedSet has non-unique keys for type $T")))
else
return expr
end
Expand Down Expand Up @@ -203,6 +220,26 @@ function Base.show(io::IO, dt::AbstractDispatchedTuple)
show_default(io, dt, dt.default)
end

# Ideally, we'd call Tuple(Set(tup)), but
# Set is backed by a Dict, which can't reside
# on the gpu, so let's get unique elements
# with Tuples:

# `a` is empty, we're done
unique_elems(a::Tuple{}, b::Tuple) = (a, b)
# `a` has one element, move `a[1]` into `b` if not unique
unique_elems(a::Tuple{A}, b::Tuple) where {A} = a[1] in b ? ((), b) : ((), (b..., a[1]))

# recurse
unique_elems(a::Tuple, b::Tuple) = a[1] in b ?
unique_elems(a[2:end], b) :
unique_elems(a[2:end], (b..., a[1]))

# Return `b`, which is the unique set of `a`
unique_elems(a::Tuple) = unique_elems(a, ())[2]

unique_keys(dt::DispatchedTuple) = unique_elems(keys(dt))

# Helper
first_eltype(::Type{Tuple{T, V}}) where {T,V} = T

Expand Down
23 changes: 14 additions & 9 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,18 @@ end
##### DispatchedSet's
#####

@testset "DispatchedTuples - unique keys" begin
tup = ()
@test DispatchedTuples.unique_elems(tup) == ()

tup = (Foo(),Bar(),Foo())
@test DispatchedTuples.unique_elems(tup) == (Foo(), Bar())

tup = ((Foo(), 1),(Bar(), 2),(Foo(), 3))
dt = DispatchedTuple(tup)
@test DispatchedTuples.unique_keys(dt) == (Foo(), Bar())
end

@testset "DispatchedSet - base behavior" begin
dt = DispatchedSet(((Foo(), 1), (Bar(), 2)))
@test dt[Foo()] == 1
Expand Down Expand Up @@ -134,15 +146,8 @@ end
end

@testset "DispatchedSet - multiple values, unique keys" begin
dt = DispatchedSet(((Foo(), 1), (Bar(), 2), (Foo(), 3)))
@test_throws ErrorException dt[Foo()] == 1
@test dt[Bar()] == 2
@test_throws ErrorException dt[FooBar()]

dt = DispatchedSet(((Foo(), 1), (Bar(), 2), (Foo(), 3)), 0)
@test_throws ErrorException dt[Foo()] == 1
@test dt[Bar()] == 2
@test dt[FooBar()] == dt.default
@test_throws ErrorException dt = DispatchedSet(((Foo(), 1), (Bar(), 2), (Foo(), 3)))
@test_throws ErrorException dt = DispatchedSet(((Foo(), 1), (Bar(), 2), (Foo(), 3)), 0)
end

@testset "DispatchedTuples - nested" begin
Expand Down

0 comments on commit c4de321

Please sign in to comment.