
begin
    """
    ```julia
    ImmutableSet{T}(xs::Set{T})
    ```
    
    Wraps around, and behaves like a regular `Set`, but mutating operations (like `push!` or `empty!`) are not allowed.
    
    When called on a set, a *shallow copy* of the set is stored. This means that it's fine to mutate the input set after creating an `ImmutableSet` from it. To prevent this, call `ImmutableSet(xs; skip_copy=true)`.
    """
	struct ImmutableSet{T} <: AbstractSet{T}
		c::Set{T}
		ImmutableSet{T}(s::Set{T}; skip_copy::Bool=false) where T = new{T}(skip_copy ? s : copy(s))
	end
	ImmutableSet(s::Set{T}; skip_copy::Bool=false) where T = ImmutableSet{T}(s; skip_copy)
	
	ImmutableSet(arg) = let
		s = Set(arg)
		ImmutableSet{eltype(s)}(s; skip_copy=true)
	end
	ImmutableSet{T}() where T = ImmutableSet{T}(Set{T}(); skip_copy=true)
	ImmutableSet() = ImmutableSet(Set(); skip_copy=true)

	Base.copy(s::ImmutableSet) = ImmutableSet(copy(s.c))
	Base.in(x, s::ImmutableSet) = Base.in(x, s.c)
	Base.isempty(s::ImmutableSet) = Base.isempty(s.c)
	Base.length(s::ImmutableSet) = Base.length(s.c)
	Base.iterate(s::ImmutableSet, i...) = Base.iterate(s.c, i...)
    Base.setdiff(s::ImmutableSet, i...) = ImmutableSet(setdiff(s.c, i...))
end

begin
    """
    ```julia
    ImmutableVector{T}(xs::Vector{T})
    ```
    
    Wraps around, and behaves like a regular `Vector`, but mutating operations (like `push!` or `setindex!`) are not allowed.
    
    When called on a vector, a *shallow copy* of the vector is stored. This means that it's fine to mutate the input vector after creating an `ImmutableVector` from it. To prevent this, call `ImmutableVector(xs; skip_copy=true)`.
    """
	struct ImmutableVector{T} <: AbstractVector{T}
		c::Vector{T}
		ImmutableVector{T}(x; skip_copy::Bool=false) where T = new{T}(skip_copy ? x : copy(x))
	end
	ImmutableVector(x::AbstractVector{T}; kwargs...) where T = ImmutableVector{T}(x; kwargs...)
	ImmutableVector{T}() where T = ImmutableVector{T}(Vector{T}())

	Base.copy(s::ImmutableVector) = ImmutableVector(copy(s.c))
	Base.in(x, s::ImmutableVector) = Base.in(x, s.c)
	Base.isempty(s::ImmutableVector) = Base.isempty(s.c)
	Base.length(s::ImmutableVector) = Base.length(s.c)
	Base.size(s::ImmutableVector) = Base.size(s.c)
	Base.iterate(s::ImmutableVector, i...) = Base.iterate(s.c, i...)
	Base.getindex(s::ImmutableVector, i::Integer) = Base.getindex(s.c, i)
	Base.getindex(s::ImmutableVector, i...) = ImmutableVector(Base.getindex(s.c, i...))
    delete_unsafe!(s::ImmutableSet, args...) = Base.delete!(s.c, args...)
end

begin
    """
    ```julia
    ImmutableDefaultDict{K,V}(default::Function, container::Dict{K,V})
    ```
    
    Wraps around, and behaves like a regular `Dict`, but if a key is not found, it will call return `default()`.
    """
    struct ImmutableDefaultDict{K,V} <: AbstractDict{K,V}
        default::Union{Function,DataType}
        container::Dict{K,V}
    end

    ImmutableDefaultDict{K,V}(default::Union{Function,DataType}) where {K,V} = ImmutableDefaultDict{K,V}(default, Dict{K,V}())

    function Base.getindex(aid::ImmutableDefaultDict{K,V}, key::K)::V where {K,V}
        get!(aid.default, aid.container, key)
    end
    function Base.merge(a1::ImmutableDefaultDict{K,V}, a2::ImmutableDefaultDict{K,V}) where {K,V}
        ImmutableDefaultDict{K,V}(a1.default, merge(a1.container, a2.container))
    end
    function Base.merge(a1::ImmutableDefaultDict{K,V}, a2::AbstractDict) where {K,V}
        ImmutableDefaultDict{K,V}(a1.default, merge(a1.container, a2))
    end
    # disabled because it's immutable!
    # Base.setindex!(aid::ImmutableDefaultDict{K,V}, args...) where {K,V} = Base.setindex!(aid.container, args...)
    # Base.delete!(aid::ImmutableDefaultDict{K,V}, args...) where {K,V} = Base.delete!(aid.container, args...)
    delete_unsafe!(aid::ImmutableDefaultDict{K,V}, args...) where {K,V} = Base.delete!(aid.container, args...)
    Base.copy(aid::ImmutableDefaultDict{K,V}) where {K,V} = ImmutableDefaultDict{K,V}(aid.default, copy(aid.container))
    Base.keys(aid::ImmutableDefaultDict) = Base.keys(aid.container)
    Base.values(aid::ImmutableDefaultDict) = Base.values(aid.container)
    Base.length(aid::ImmutableDefaultDict) = Base.length(aid.container)
    Base.iterate(aid::ImmutableDefaultDict, args...) = Base.iterate(aid.container, args...)
end

"""
```julia
setdiffkeys(d::Dict{K,V}, key_itrs...)::Dict{K,V}
```

Apply `setdiff` on the keys of a dictionary.

# Example
```julia
setdiffkeys(Dict(1 => "one", 2 => "two", 3 => "three"), [1, 3])
# result: `Dict(2 => "two")`
```
"""
setdiffkeys(d::Dict{K,V}, key_itrs...) where {K,V} = Dict{K,V}(k => d[k] for k in setdiff(keys(d), key_itrs...))
setdiffkeys(d::ImmutableDefaultDict{K,V}, key_itrs...) where {K,V} = ImmutableDefaultDict{K,V}(d.default, setdiffkeys(d.container, key_itrs...))
