-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add some more interfaces to BaseInterfaces.jl (#27)
* add some more interfaces * fix set * dont show iteration tests * copy objects in testing * more interface tests * test and document BaseInterfaces in CI * use local path * add BaseInterfaces docs page * dont dev the project * no Pairs * fix md path * add readme to BaseInterfaces * test Base.isiterable * add supertypes * updates * more comments and fixes from review * fix Dicts * fix args * fix basics tests * fix for 1.6
- Loading branch information
Showing
18 changed files
with
572 additions
and
141 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
name: Documentation | ||
|
||
on: | ||
push: | ||
branches: | ||
- main # update to match your development branch (master, main, dev, trunk, ...) | ||
tags: '*' | ||
pull_request: | ||
|
||
jobs: | ||
build: | ||
permissions: | ||
contents: write | ||
statuses: write | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v4 | ||
- uses: julia-actions/setup-julia@v1 | ||
with: | ||
version: '1' | ||
- name: Install dependencies | ||
run: julia --project=docs/ -e 'using Pkg; pkg"dev ."; pkg"dev ./BaseInterfaces"; Pkg.instantiate()' | ||
- name: Build and deploy | ||
env: | ||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # If authenticating with GitHub Actions token | ||
DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} # If authenticating with SSH deploy key | ||
run: julia --project=docs/ docs/make.jl |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,36 @@ | ||
# BaseInterfaces | ||
|
||
[![Stable](https://img.shields.io/badge/docs-stable-blue.svg)](https://rafaqz.github.io/BaseInterfaces.jl/stable/) | ||
[![Dev](https://img.shields.io/badge/docs-dev-blue.svg)](https://rafaqz.github.io/BaseInterfaces.jl/dev/) | ||
[![Build Status](https://github.com/rafaqz/BaseInterfaces.jl/actions/workflows/CI.yml/badge.svg?branch=main)](https://github.com/rafaqz/BaseInterfaces.jl/actions/workflows/CI.yml?query=branch%3Amain) | ||
[![Coverage](https://codecov.io/gh/rafaqz/BaseInterfaces.jl/branch/main/graph/badge.svg)](https://codecov.io/gh/rafaqz/BaseInterfaces.jl) | ||
[![Stable](https://img.shields.io/badge/docs-stable-blue.svg)](https://rafaqz.github.io/Interfaces.jl/stable/) | ||
[![Dev](https://img.shields.io/badge/docs-dev-blue.svg)](https://rafaqz.github.io/Interfaces.jl/dev/) | ||
[![Build Status](https://github.com/rafaqz/Interfaces.jl/actions/workflows/CI.yml/badge.svg?branch=main)](https://github.com/rafaqz/Interfaces.jl/actions/workflows/CI.yml?query=branch%3Amain) | ||
[![Coverage](https://codecov.io/gh/rafaqz/Interfaces.jl/branch/main/graph/badge.svg)](https://codecov.io/gh/rafaqz/Interfaces.jl) | ||
|
||
BaseInterfaces.jl is a subpackage of Interfaces.jl that provides predifined | ||
definition and testing for Base Julia interfaces. | ||
|
||
Currently this includes: | ||
- A general iteration interface: `IterationInterface` | ||
- `AbstractArray` interface: `ArrayInterface` | ||
- `AbstractSet` interface: `SetInterface` | ||
- `AbstractDict` interface: `DictInterface` | ||
|
||
|
||
Testing your object follows the interfaces is as simple as: | ||
|
||
```julia | ||
using BaseInterfaces, Interfaces | ||
Interfaces.tests(DictInterface, MyDict, [mydict1, mydict2, ...]) | ||
``` | ||
|
||
Declaring that it follows the interface is done with: | ||
|
||
```julia | ||
@implements DictInterface{(:component1, :component2)} MyDict | ||
``` | ||
|
||
Where components can be chosen from `Interfaces.optional_keys(DictInterface)`. | ||
|
||
See [the docs](https://rafaqz.github.io/Interfaces.jl/stable/) for use. | ||
|
||
If you want to add more Base julia interfaces here, or think the existing | ||
ones could be improved, please make an issue or pull request. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
#= Abstract Arrays | ||
https://docs.julialang.org/en/v1/manual/interfaces/ | ||
Methods to implement Brief description | ||
size(A) Returns a tuple containing the dimensions of A | ||
getindex(A, i::Int) (if IndexLinear) Linear scalar indexing | ||
getindex(A, I::Vararg{Int, N}) (if IndexCartesian, where N = ndims(A)) N-dimensional scalar indexing | ||
Optional methods Default definition Brief description | ||
IndexStyle(::Type) IndexCartesian() Returns either IndexLinear() or IndexCartesian(). See the description below. | ||
setindex!(A, v, i::Int) (if IndexLinear) Scalar indexed assignment | ||
setindex!(A, v, I::Vararg{Int, N}) (if IndexCartesian, where N = ndims(A)) N-dimensional scalar indexed assignment | ||
getindex(A, I...) defined in terms of scalar getindex Multidimensional and nonscalar indexing | ||
setindex!(A, X, I...) defined in terms of scalar setindex! Multidimensional and nonscalar indexed assignment | ||
iterate defined in terms of scalar getindex Iteration | ||
length(A) prod(size(A)) Number of elements | ||
similar(A) similar(A, eltype(A), size(A)) Return a mutable array with the same shape and element type | ||
similar(A, ::Type{S}) similar(A, S, size(A)) Return a mutable array with the same shape and the specified element type | ||
similar(A, dims::Dims) similar(A, eltype(A), dims) Return a mutable array with the same element type and size dims | ||
similar(A, ::Type{S}, dims::Dims) Array{S}(undef, dims) Return a mutable array with the specified element type and size | ||
Non-traditional indices Default definition Brief description | ||
axes(A) map(OneTo, size(A)) Return a tuple of AbstractUnitRange{<:Integer} of valid indices | ||
similar(A, ::Type{S}, inds) similar(A, S, Base.to_shape(inds)) Return a mutable array with the specified indices inds (see below) | ||
similar(T::Union{Type,Function}, inds) T(Base.to_shape(inds)) Return an array similar to T with the specified indices inds (see below) | ||
=# | ||
|
||
# And arbitrary new type for array values | ||
struct ArrayTestVal | ||
a::Int | ||
end | ||
|
||
# In case `eltype` and `ndims` have custom methods | ||
# We should always be able to use these to mean the same thing | ||
_eltype(::AbstractArray{T}) where T = T | ||
_ndims(::AbstractArray{<:Any,N}) where N = N | ||
|
||
array_components = (; | ||
mandatory = (; | ||
type = A -> A isa AbstractArray, | ||
eltype = ( | ||
A -> eltype(A) isa Type, | ||
A -> eltype(A) == _eltype(A), | ||
), | ||
ndims = ( | ||
A -> ndims(A) isa Int, | ||
A -> ndims(A) == _ndims(A), | ||
), | ||
size = ( | ||
"size(A) returns a tuple of Integer" => A -> size(A) isa NTuple{<:Any,Integer}, | ||
"length of size(A) matches ndims(A)" => A -> length(size(A)) == ndims(A), | ||
), | ||
getindex = ( | ||
"Can index with begin/firstinex" => A -> A[begin] isa eltype(A), | ||
"Can index with end/lastindex" => A -> A[end] isa eltype(A), | ||
"Can index with all indices in `eachindex(A)`" => A -> all(x -> A[x] isa eltype(A), eachindex(A)), | ||
"Can index with multiple dimensions" => A -> A[map(first, axes(A))...] isa eltype(A), | ||
"Can use trailing ones" => A -> A[map(first, axes(A))..., 1, 1, 1] isa eltype(A), | ||
"Can index with CartesianIndex" => A -> A[CartesianIndex(map(first, axes(A))...)] isa eltype(A), | ||
"Can use trailing ones in CartesianIndex" => A -> A[CartesianIndex(map(first, axes(A))..., 1, 1, 1)] isa eltype(A), | ||
), | ||
indexstyle = "IndexStyle returns IndexCartesian or IndexLinear" => A -> IndexStyle(A) in (IndexCartesian(), IndexLinear()), | ||
), | ||
# TODO implement all the optional conditions | ||
optional = (; | ||
setindex! = ( | ||
A -> length(A) > 1 || throw(ArgumentError("Test arrays must have more than one element to test setindex!")), | ||
"setindex! can write the first to the last element" => | ||
A -> begin | ||
x1 = A[begin]; x2 = A[end] | ||
A[begin] = x2 | ||
A[end] = x1 | ||
A[begin] == x2 && A[end] == x1 | ||
end, | ||
"setindex! can write the first to the last element using multidimensional indices" => | ||
A -> begin | ||
fs = map(first, axes(A)) | ||
ls = map(last, axes(A)) | ||
x1 = A[fs...]; | ||
x2 = A[ls...] | ||
A[fs...] = x2 | ||
A[ls...] = x1 | ||
A[fs...] == x2 && A[ls...] == x1 | ||
end, | ||
"setindex! can write to all indices in eachindex(A)" => | ||
A -> begin | ||
v = first(A) | ||
all(eachindex(A)) do i | ||
A[i] = v | ||
A[i] === v | ||
end | ||
end, | ||
"setindex! can write to all indices in CartesianIndices(A)" => | ||
A -> begin | ||
v = first(A) # We have already tested writing to the first index above | ||
all(CartesianIndices(A)) do i | ||
A[i] = v | ||
A[i] === v | ||
end | ||
end, | ||
), | ||
similar_type = "`similar(A)` returns an object the same type and size as `A`" => | ||
A -> begin | ||
A1 = similar(A) | ||
A1 isa typeof(A) && size(A1) == size(A) | ||
end, | ||
similar_eltype = "similar(A, T::Type) returns an object the same base type as `A` with eltype of `T`" => | ||
A -> begin | ||
A1 = similar(A, ArrayTestVal); | ||
_wrappertype(A) == _wrappertype(A1) && eltype(A1) == ArrayTestVal && size(A) == size(A1) | ||
end, | ||
similar_size = "similar(A, s::NTuple{Int}) returns an object the same type as `A` with size `s`" => | ||
A -> begin | ||
A1 = similar(A, (2, 3)) | ||
A2 = similar(A, (4, 5)) | ||
_wrappertype(A) == _wrappertype(A1) && size(A1) == (2, 3) && size(A2) == (4, 5) | ||
end, | ||
similar_eltype_size = "similar(A, T::Type, s::NTuple{Int}) returns an object the same type as `A` with eltype `T` and size `s`" => | ||
A -> begin | ||
A1 = similar(A, ArrayTestVal, (2, 3)) | ||
A2 = similar(A, ArrayTestVal, (4, 5)) | ||
_wrappertype(A) == _wrappertype(A1) && eltype(A1) == ArrayTestVal && size(A1) == (2, 3) && size(A2) == (4, 5) | ||
end, | ||
) | ||
) | ||
|
||
_wrappertype(A) = Base.typename(typeof(A)).wrapper | ||
|
||
@interface ArrayInterface AbstractArray array_components "Base Julia AbstractArray interface" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
# Should iteration be here? | ||
|
||
mandatory = (; | ||
isempty = !isempty, | ||
) | ||
|
||
optional = (; | ||
empty! = c -> isempty(empty!(c)), | ||
length = c -> length(c) isa Integer, | ||
# push! = | ||
# pushfirst! = | ||
# deleteat! = | ||
# splice! = | ||
# pop! = | ||
# popfirst! = | ||
) | ||
|
||
components = (; mandatory, optional) | ||
|
||
@interface CollectionInterface Any _components "Base interface for shared methods of various collections" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
|
||
@interface DictInterface AbstractDict ( # <: CollectionInterface | ||
mandatory = (; | ||
iterate = "AbstractDict follows the IterationInterface" => a -> Interfaces.test(IterationInterface, a.d; show=false) && first(iterate(a.d)) isa Pair, | ||
eltype = "eltype is a Pair" => a -> eltype(a.d) <: Pair, | ||
keytype = a -> keytype(a.d) == eltype(a.d).parameters[1], | ||
valtype = a -> valtype(a.d) == eltype(a.d).parameters[2], | ||
keys = a -> all(k -> k isa keytype(a.d), keys(a.d)), | ||
values = a -> all(v -> v isa valtype(a.d), values(a.d)), | ||
getindex = ( | ||
a -> a.d[first(keys(a.d))] === first(values(a.d)), | ||
a -> all(k -> a.d[k] isa valtype(a.d), keys(a.d)), | ||
), | ||
), | ||
optional = (; | ||
setindex! = ( | ||
"test object `d` does not yet have test key `k`" => a -> !haskey(a.d, a.k), | ||
"can set key `k` to value `v`" => a -> (a.d[a.k] = a.v; a.d[a.k] == a.v), | ||
), | ||
) | ||
) """ | ||
`AbstractDict` interface requires Arguments, with `d = the_dict` mandatory, and | ||
when `setindex` is needed, `k = any_valid_key_not_in_d, v = any_valid_val` | ||
""" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
# Some example interface delarations. | ||
|
||
# @implements ArrayInterface Base.LogicalIndex # No getindex | ||
@implements ArrayInterface UnitRange | ||
@implements ArrayInterface StepRange | ||
@implements ArrayInterface Base.Slice | ||
@implements ArrayInterface Base.IdentityUnitRange | ||
@implements ArrayInterface Base.CodeUnits | ||
@implements ArrayInterface{(:setindex!,:similar_type,:similar_eltype,:similar_size)} Array | ||
@implements ArrayInterface{(:setindex!,:similar_type,:similar_size)} BitArray | ||
@implements ArrayInterface{:setindex!} SubArray | ||
@implements ArrayInterface{:setindex!} PermutedDimsArray | ||
@implements ArrayInterface{:setindex!} Base.ReshapedArray | ||
|
||
@implements DictInterface{:setindex!} Dict | ||
@implements DictInterface{:setindex!} IdDict | ||
@implements DictInterface{:setindex!} WeakKeyDict | ||
@implements DictInterface Base.EnvDict | ||
@implements DictInterface Base.ImmutableDict | ||
@static if VERSION >= v"1.9.0" | ||
@implements DictInterface Base.Pairs | ||
end | ||
|
||
@implements IterationInterface{(:reverse,:indexing)} UnitRange | ||
@implements IterationInterface{(:reverse,:indexing)} StepRange | ||
@implements IterationInterface{(:reverse,:indexing)} Array | ||
@implements IterationInterface{(:reverse,:indexing)} Tuple | ||
@implements IterationInterface{(:reverse,:indexing)} NamedTuple | ||
@implements IterationInterface{(:reverse,:indexing)} String | ||
@implements IterationInterface{(:reverse,:indexing)} Pair | ||
@implements IterationInterface{(:reverse,:indexing)} Number | ||
@implements IterationInterface{(:reverse,:indexing)} Base.EachLine | ||
@implements IterationInterface{(:reverse,)} Base.Generator | ||
@implements IterationInterface Set | ||
@implements IterationInterface BitSet | ||
@implements IterationInterface IdDict | ||
@implements IterationInterface Dict | ||
@implements IterationInterface WeakKeyDict | ||
|
||
# TODO add grouping to reduce the number of options | ||
@implements SetInterface{(:copy,:empty,:emptymutable,:hasfastin,:setdiff,:intersect,:union,:empty!,:delete!,:push!,:copymutable,:sizehint!)} Set | ||
@implements SetInterface{(:copy,:empty,:emptymutable,:hasfastin,:setdiff,:intersect,:union,:empty!,:delete!,:push!,:copymutable,:sizehint!)} BitSet | ||
@implements SetInterface{(:empty,:emptymutable,:hasfastin,:intersect,:union,:sizehint!)} Base.KeySet |
Oops, something went wrong.