Skip to content

Proof-of-concept: _implements trait #1957

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions src/ConformanceTests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,63 @@
function generate_element end


###############################################################################
#
# `implements` trait
#
###############################################################################

# Calling `_implements(T, f)` checks whether a "sensible" method for the unary
# function `f` is implemented for inputs of type `T`. The argument order is
# meant to be similar to e.g. `isa`, and thus indicates `T implements f`.
#
# For example, `_implements(MyRingElem, is_unit)` should return true if
# invoking `is_unit` on elements of type `MyRingElem` is supported.
#
# The generic fallback uses `hasmethod`. However, this may return `true` in
# cases where it shouldn't, as we often provide generic methods for that rely
# on other methods being implemented -- either for the same type, or for types
# derived from it. For example the `is_nilpotent(::PolyElem{T})` method needs
# `is_nilpotent(::T)` in order to work.
#
# To reflect this, additional `_implements` methods need to be provided.
# We currently do this for at least the following functions:
# - factor
# - is_irreducible
# - is_nilpotent
# - is_squarefree
# - is_unit
# - is_zero_divisor
#
_implements(::Type{T}, f::Any) where {T} = hasmethod(f, Tuple{T})

Check warning on line 74 in src/ConformanceTests.jl

View check run for this annotation

Codecov / codecov/patch

src/ConformanceTests.jl#L74

Added line #L74 was not covered by tests

# Alternatively, the first argument can be a concrete object. By default we
# then redispatch to the type based version. But one may also choose to
# implement custom methods for this: certain operations will only work for
# *some* instances. E.g. for `Z/nZ` it may happen that for `n` a prime we can
# perform a certain operation, but not if `n` is composite.
#
# In that case the recommendation is that `_implements` invoked on the type
# returns `false`, but invoked on a concrete instance of a type, it may use
# specifics of the instance to also return `true` if appropriate.
function _implements(x::T, f::Any) where {T}
@assert !(x isa Type) # paranoia
return _implements(T, f)

Check warning on line 87 in src/ConformanceTests.jl

View check run for this annotation

Codecov / codecov/patch

src/ConformanceTests.jl#L85-L87

Added lines #L85 - L87 were not covered by tests
end

# helper for `_implements` which checks if `f` has a method explicitly for
# a concrete type `T` (i.e. not a generic method that can be specialized to `T`
# but really one that is implement for `T` and `T` only).
function _implements_directly(::Type{T}, f::Any) where {T}
isconcretetype(T) || return false # TODO: drop this?
meth = methods(f, Tuple{T})

Check warning on line 95 in src/ConformanceTests.jl

View check run for this annotation

Codecov / codecov/patch

src/ConformanceTests.jl#L93-L95

Added lines #L93 - L95 were not covered by tests
# TODO: deal with type parameters: if `T` is `FreeAssociativeAlgebraElem{ZZRingElem}`
# and `f` has a method for `FreeAssociativeAlgebraElem` then we should still consider
# this a match.
return any(m -> m.sig == Tuple{typeof(f), T}, meth)

Check warning on line 99 in src/ConformanceTests.jl

View check run for this annotation

Codecov / codecov/patch

src/ConformanceTests.jl#L99

Added line #L99 was not covered by tests
end


###############################################################################
#
# The following function stubs' actual implementations are in the folder `ext/TestExt/`.
Expand Down
2 changes: 2 additions & 0 deletions src/FreeAssociativeAlgebra.jl
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@
end
end

ConformanceTests._implements(::Type{FreeAssociativeAlgebraElem{T}}, f::typeof(is_unit)) where T = is_domain_type(T) || _implements_directly(T, f)

Check warning on line 139 in src/FreeAssociativeAlgebra.jl

View check run for this annotation

Codecov / codecov/patch

src/FreeAssociativeAlgebra.jl#L139

Added line #L139 was not covered by tests

###############################################################################
#
# Hashing
Expand Down
6 changes: 6 additions & 0 deletions src/MPoly.jl
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,8 @@
return constant_term_is_unit # handles the case that there is no constant term
end

ConformanceTests._implements(::Type{MPolyRingElem{T}}, ::typeof(is_unit)) where T = _implements(T, is_unit) && _implements(T, is_nilpotent)

Check warning on line 440 in src/MPoly.jl

View check run for this annotation

Codecov / codecov/patch

src/MPoly.jl#L440

Added line #L440 was not covered by tests

function content(a::MPolyRingElem{T}) where T <: RingElement
z = zero(coefficient_ring(a))
for c in coefficients(a)
Expand All @@ -452,12 +454,16 @@
return all(is_nilpotent, coefficients(f))
end

ConformanceTests._implements(::Type{MPolyRingElem{T}}, ::typeof(is_nilpotent)) where T = _implements(T, is_nilpotent)

Check warning on line 457 in src/MPoly.jl

View check run for this annotation

Codecov / codecov/patch

src/MPoly.jl#L457

Added line #L457 was not covered by tests


function is_zero_divisor(x::MPolyRingElem{T}) where T <: RingElement
is_domain_type(T) && return is_zero(x)
return is_zero_divisor(content(x))
end

ConformanceTests._implements(::Type{MPolyRingElem{T}}, ::typeof(is_zero_divisor)) where T = _implements(T, is_zero_divisor)

Check warning on line 465 in src/MPoly.jl

View check run for this annotation

Codecov / codecov/patch

src/MPoly.jl#L465

Added line #L465 was not covered by tests

function is_zero_divisor_with_annihilator(a::MPolyRingElem{T}) where T <: RingElement
f, b = is_zero_divisor_with_annihilator(content(a))
return f, parent(a)(b)
Expand Down
4 changes: 4 additions & 0 deletions src/MatRing.jl
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,15 @@

is_unit(a::MatRingElem{T}) where T <: FieldElement = rank(a) == degree(a)

ConformanceTests._implements(::Type{MatRingElem{T}}, ::typeof(is_unit)) where {T <: RingElement} = _implements(T, is_unit)

Check warning on line 57 in src/MatRing.jl

View check run for this annotation

Codecov / codecov/patch

src/MatRing.jl#L57

Added line #L57 was not covered by tests

# proof over a commutative ring: use adj(A)*A = det(A)*I = A*adj(A)
is_zero_divisor(a::MatRingElem{T}) where T <: RingElement = is_zero_divisor(det(a))

is_zero_divisor(a::MatRingElem{T}) where T <: FieldElement = rank(a) != degree(a)

ConformanceTests._implements(::Type{MatRingElem{T}}, ::typeof(is_zero_divisor)) where {T <: RingElement} = _implements(T, is_zero_divisor)

Check warning on line 64 in src/MatRing.jl

View check run for this annotation

Codecov / codecov/patch

src/MatRing.jl#L64

Added line #L64 was not covered by tests

function is_zero_divisor_with_annihilator(a::MatRingElem{T}) where T <: RingElement
f, b = is_zero_divisor_with_annihilator(det(a))
throw(NotImplementedError(:adj, a)) #return f, b*adj(A)
Expand Down
2 changes: 2 additions & 0 deletions src/NCRings.jl
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,8 @@
throw(NotImplementedError(:is_nilpotent, a))
end

ConformanceTests._implements(::Type{T}, f::typeof(is_nilpotent)) where {T <: NCRingElement} = is_domain_type(T) || _implements_directly(T, f)

Check warning on line 178 in src/NCRings.jl

View check run for this annotation

Codecov / codecov/patch

src/NCRings.jl#L178

Added line #L178 was not covered by tests


###############################################################################
#
Expand Down
5 changes: 5 additions & 0 deletions src/Poly.jl
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,8 @@
return true
end

ConformanceTests._implements(::Type{PolynomialElem{T}}, ::typeof(is_unit)) where T = _implements(T, is_unit) && _implements(T, is_nilpotent)

Check warning on line 246 in src/Poly.jl

View check run for this annotation

Codecov / codecov/patch

src/Poly.jl#L246

Added line #L246 was not covered by tests

function is_nilpotent(f::T) where {T <: PolyRingElem}
# Makes essential use of the fact that sum of 2 nilpotents is nilpotent.
# This is true when the coeffs are commutative.
Expand All @@ -252,9 +254,12 @@
return all(is_nilpotent, coefficients(f))
end

ConformanceTests._implements(::Type{PolynomialElem{T}}, ::typeof(is_nilpotent)) where T = _implements(T, is_nilpotent)

Check warning on line 257 in src/Poly.jl

View check run for this annotation

Codecov / codecov/patch

src/Poly.jl#L257

Added line #L257 was not covered by tests

is_zero_divisor(a::PolynomialElem) = is_zero_divisor(content(a))

ConformanceTests._implements(::Type{PolynomialElem{T}}, ::typeof(is_zero_divisor)) where T = _implements(T, is_zero_divisor)

Check warning on line 261 in src/Poly.jl

View check run for this annotation

Codecov / codecov/patch

src/Poly.jl#L261

Added line #L261 was not covered by tests

function is_zero_divisor_with_annihilator(a::PolyRingElem{T}) where T <: RingElement
f, b = is_zero_divisor_with_annihilator(content(a))
return f, parent(a)(b)
Expand Down
9 changes: 9 additions & 0 deletions src/algorithms/GenericFunctions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,8 @@
return is_zero(a) && !is_trivial(parent(a))
end

ConformanceTests._implements(::Type{T}, f::typeof(is_zero_divisor)) where {T} = is_domain_type(T) || _implements_directly(T, f)

Check warning on line 432 in src/algorithms/GenericFunctions.jl

View check run for this annotation

Codecov / codecov/patch

src/algorithms/GenericFunctions.jl#L432

Added line #L432 was not covered by tests

@doc raw"""
is_zero_divisor_with_annihilator(a::T) where T <: RingElement

Expand Down Expand Up @@ -456,6 +458,8 @@
throw(NotImplementedError(:factor, a))
end

ConformanceTests._implements(::Type{T}, f::typeof(factor)) where {T} = _implements_directly(T, f)

Check warning on line 461 in src/algorithms/GenericFunctions.jl

View check run for this annotation

Codecov / codecov/patch

src/algorithms/GenericFunctions.jl#L461

Added line #L461 was not covered by tests

@doc raw"""
factor_squarefree(a::T) where T <: RingElement -> Fac{T}

Expand All @@ -466,6 +470,8 @@
throw(NotImplementedError(:factor_squarefree, a))
end

ConformanceTests._implements(::Type{T}, f::typeof(factor_squarefree)) where {T} = _implements_directly(T, f)

Check warning on line 473 in src/algorithms/GenericFunctions.jl

View check run for this annotation

Codecov / codecov/patch

src/algorithms/GenericFunctions.jl#L473

Added line #L473 was not covered by tests

@doc raw"""
is_irreducible(a::RingElement)

Expand All @@ -479,6 +485,8 @@
return length(af) == 1 && all(isone, values(af.fac))
end

ConformanceTests._implements(::Type{T}, ::typeof(is_irreducible)) where {T} = _implements(T, is_unit) && _implements(T, factor)

Check warning on line 488 in src/algorithms/GenericFunctions.jl

View check run for this annotation

Codecov / codecov/patch

src/algorithms/GenericFunctions.jl#L488

Added line #L488 was not covered by tests

@doc raw"""
is_squarefree(a::RingElement)

Expand All @@ -493,3 +501,4 @@
return all(isone, values(af.fac))
end

ConformanceTests._implements(::Type{T}, ::typeof(is_squarefree)) where {T} = _implements(T, is_unit) && _implements(T, factor_squarefree)

Check warning on line 504 in src/algorithms/GenericFunctions.jl

View check run for this annotation

Codecov / codecov/patch

src/algorithms/GenericFunctions.jl#L504

Added line #L504 was not covered by tests
4 changes: 4 additions & 0 deletions src/algorithms/LaurentPoly.jl
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,10 @@
return is_nilpotent(f.poly);
end

ConformanceTests._implements(::Type{LaurentPolyRingElem{T}}, ::typeof(is_unit)) where T = _implements(T, is_unit) && _implements(T, is_nilpotent)

Check warning on line 162 in src/algorithms/LaurentPoly.jl

View check run for this annotation

Codecov / codecov/patch

src/algorithms/LaurentPoly.jl#L162

Added line #L162 was not covered by tests

ConformanceTests._implements(::Type{LaurentPolyRingElem{T}}, ::typeof(is_nilpotent)) where T = _implements(T, is_nilpotent)

Check warning on line 164 in src/algorithms/LaurentPoly.jl

View check run for this annotation

Codecov / codecov/patch

src/algorithms/LaurentPoly.jl#L164

Added line #L164 was not covered by tests


###############################################################################
#
Expand Down
4 changes: 4 additions & 0 deletions src/generic/LaurentMPoly.jl
Original file line number Diff line number Diff line change
Expand Up @@ -126,14 +126,18 @@
return unit_seen
end

ConformanceTests._implements(::Type{LaurentMPolyRingElem{T}}, ::typeof(is_unit)) where T = _implements(T, is_unit) && _implements(T, is_nilpotent)

Check warning on line 129 in src/generic/LaurentMPoly.jl

View check run for this annotation

Codecov / codecov/patch

src/generic/LaurentMPoly.jl#L129

Added line #L129 was not covered by tests

function is_nilpotent(f::T) where {T <: LaurentMPolyRingElem}
return is_nilpotent(f.mpoly);
end

ConformanceTests._implements(::Type{LaurentMPolyRingElem{T}}, ::typeof(is_nilpotent)) where T = _implements(T, is_nilpotent)

Check warning on line 135 in src/generic/LaurentMPoly.jl

View check run for this annotation

Codecov / codecov/patch

src/generic/LaurentMPoly.jl#L135

Added line #L135 was not covered by tests

is_zero_divisor(p::LaurentMPolyWrap) = is_zero_divisor(p.mpoly)

ConformanceTests._implements(::Type{LaurentMPolyRingElem{T}}, ::typeof(is_zero_divisor)) where T = _implements(T, is_zero_divisor)

Check warning on line 139 in src/generic/LaurentMPoly.jl

View check run for this annotation

Codecov / codecov/patch

src/generic/LaurentMPoly.jl#L139

Added line #L139 was not covered by tests

function is_zero_divisor_with_annihilator(p::LaurentMPolyWrap)
f, b = is_zero_divisor_with_annihilator(p.mpoly)
return f, LaurentMPolyWrap(parent(p), b)
Expand Down
Loading