Skip to content

Commit

Permalink
Merge pull request #27 from putianyi889/version-0.0.5.1
Browse files Browse the repository at this point in the history
Change method names from `**convert` to `convert_**type`
  • Loading branch information
putianyi889 authored Dec 21, 2024
2 parents 170cbd9 + e6d1dab commit b16ba57
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 65 deletions.
34 changes: 17 additions & 17 deletions docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,19 @@ We note that this package has some overlap with [TypeUtils.jl](https://github.co

## Introduction

### `elconvert` and `_to_eltype`
`elconvert(T, x)` works like `convert(T, x)`, except that `T` refers to the eltype of the result. This can be useful for generic codes.
### `convert_eltype` and `_to_eltype`
`convert_eltype(T, x)` works like `convert(T, x)`, except that `T` refers to the eltype of the result. This can be useful for generic codes.

It should be always true that `elconvert(T, x) isa _to_eltype(T, typeof(x))`. However, since `elconvert` and `_to_eltype` use different routines, it's possible that the equality doesn't hold for some types. Please submit an issue or PR if that happens.
It should be always true that `convert_eltype(T, x) isa _to_eltype(T, typeof(x))`. However, since `convert_eltype` and `_to_eltype` use different routines, it's possible that the equality doesn't hold for some types. Please submit an issue or PR if that happens.

If `typeof(x)` is not in Base or stdlib, the package who owns the type should implement corresponding `_to_eltype` or `elconvert`. `elconvert` has fallbacks, in which case it could be unnecessary:
- For a subtype of `AbstractArray`, `elconvert` calls the constructor `AbstractArray{T}` and `_to_eltype` returns `Array`.
- For a subtype of `AbstractUnitRange`, `elconvert` calls the constructor `AbstractUnitRange{T}`.
- For a subtype of `AbstractRange`, `elconvert` uses broadcast through `map`.
- For a `Tuple`, `elconvert` uses dot broadcast.
- For other types, `elconvert` calls `convert` and `_to_eltype`.
If `typeof(x)` is not in Base or stdlib, the package who owns the type should implement corresponding `_to_eltype` or `convert_eltype`. `convert_eltype` has fallbacks, in which case it could be unnecessary:
- For a subtype of `AbstractArray`, `convert_eltype` calls the constructor `AbstractArray{T}` and `_to_eltype` returns `Array`.
- For a subtype of `AbstractUnitRange`, `convert_eltype` calls the constructor `AbstractUnitRange{T}`.
- For a subtype of `AbstractRange`, `convert_eltype` uses broadcast through `map`.
- For a `Tuple`, `convert_eltype` uses dot broadcast.
- For other types, `convert_eltype` calls `convert` and `_to_eltype`.

However, `_to_eltype` must be implemented for each type to support `baseconvert` and `precisionconvert`. The following types from Base and stdlib are explicitly supported by `_to_eltype`:
However, `_to_eltype` must be implemented for each type to support `convert_basetype` and `convert_precisiontype`. The following types from Base and stdlib are explicitly supported by `_to_eltype`:
```
AbstractArray, AbstractDict, AbstractSet, Adjoint, Bidiagonal, BitArray, CartesianIndices, Diagonal, Dict, Hermitian, Set, StepRangeLen, Symmetric, SymTridiagonal, Transpose, TwicePrecision, UnitRange
```
Expand All @@ -37,23 +37,23 @@ precisiontype(Set{Matrix{Vector{Matrix{Complex{Rational{Int}}}}}})
- `sometype(T)` gets the `sometype` of type `T`.
- `sometype(x) = sometype(typeof(x))` is also provided for convenience.
- `_to_sometype(T,S)` converts the type `S` to have the `sometype` of `T`.
- `someconvert(T,A)` converts `A` to have the `sometype` of `T`.
- `convert_sometype(T,A)` converts `A` to have the `sometype` of `T`.

where `some` can be `el`, `base` and `precision`.

### On `precisionconvert`
`precisionconvert` accepts an optional third argument `prec`.
### On `convert_precisiontype`
`convert_precisiontype` accepts an optional third argument `prec`.
- When `T` has static precision, `prec` has no effect.
- When `T` has dynamic precision, `prec` specifies the precision of conversion. When `prec` is not provided, the precision is decided by the external setup from `T`. The difference is significant when `precisionconvert` is called by another function:
- When `T` has dynamic precision, `prec` specifies the precision of conversion. When `prec` is not provided, the precision is decided by the external setup from `T`. The difference is significant when `convert_precisiontype` is called by another function:
```@repl 1
precision(BigFloat)
f(x) = precisionconvert(BigFloat, x, 256)
g(x) = precisionconvert(BigFloat, x)
f(x) = convert_precisiontype(BigFloat, x, 256)
g(x) = convert_precisiontype(BigFloat, x)
setprecision(128)
f(π) # static precision
g(π) # precision varies with the global setting
```
- When `T` is an integer, the conversion will dig into `Rational` as well. In contrast, since `Rational` as a whole is more "precise" than an integer, `precisiontype` doesn't unwrap `Rational`.
```@repl 1
precisiontype(precisionconvert(Int128, Int8(1)//Int8(2)))
precisiontype(convert_precisiontype(Int128, Int8(1)//Int8(2)))
```
63 changes: 32 additions & 31 deletions src/EltypeExtensions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,52 +4,52 @@ import Base: convert, TwicePrecision
using LinearAlgebra # to support 1.0, not using package extensions
import LinearAlgebra: AbstractQ

export elconvert, basetype, baseconvert, precisiontype, precisionconvert
export convert_eltype, basetype, convert_basetype, precisiontype, convert_precisiontype

@static if VERSION >= v"1.3"
_to_eltype(::Type{T}, ::Type{UpperHessenberg{S,M}}) where {T,S,M} = UpperHessenberg{T,_to_eltype(T,M)}
elconvert(::Type{T}, A::UpperHessenberg{S,M}) where {T,S,M} = UpperHessenberg{T,_to_eltype(T,M)}(A)
convert_eltype(::Type{T}, A::UpperHessenberg{S,M}) where {T,S,M} = UpperHessenberg{T,_to_eltype(T,M)}(A)
end
@static if VERSION >= v"1.5"
@inline bigfloatconvert(x, prec) = BigFloat(x, precision = prec)
else
@inline bigfloatconvert(x, prec) = BigFloat(x, prec)
end
@static if VERSION >= v"1.9"
elconvert(::Type{T}, A::S) where {T,S<:Bidiagonal} = convert(_to_eltype(T, S), A)
convert_eltype(::Type{T}, A::S) where {T,S<:Bidiagonal} = convert(_to_eltype(T, S), A)
else
elconvert(::Type{T}, A::Bidiagonal{S,V}) where {T,S,V} = Bidiagonal{T,_to_eltype(T,V)}(A.dv, A.ev, A.uplo)
convert_eltype(::Type{T}, A::Bidiagonal{S,V}) where {T,S,V} = Bidiagonal{T,_to_eltype(T,V)}(A.dv, A.ev, A.uplo)
end
@static if VERSION >= v"1.10"
elconvert(::Type{T}, A::AbstractQ) where T = convert(AbstractQ{T}, A) # see https://github.com/JuliaLang/julia/pull/46196
convert_eltype(::Type{T}, A::AbstractQ) where T = convert(AbstractQ{T}, A) # see https://github.com/JuliaLang/julia/pull/46196
end

"""
elconvert(T, A)
convert_eltype(T, A)
Similar to `convert(T, A)`, but `T` refers to the eltype. See also [`_to_eltype`](@ref).
# Examples
```jldoctest; setup = :(using EltypeExtensions: elconvert)
julia> elconvert(Float64, 1:10)
```jldoctest; setup = :(using EltypeExtensions: convert_eltype)
julia> convert_eltype(Float64, 1:10)
1.0:1.0:10.0
julia> typeof(elconvert(Float64, rand(Int, 3, 3)))
julia> typeof(convert_eltype(Float64, rand(Int, 3, 3)))
$(repr("text/plain", Matrix{Float64}))
```
"""
elconvert(::Type{T}, A::S) where {T,S} = convert(_to_eltype(T, S), A)
elconvert(::Type{T}, A::AbstractArray) where T = convert(AbstractArray{T}, A)
elconvert(::Type{T}, A::AbstractRange) where T = map(T, A)
elconvert(::Type{T}, A::AbstractUnitRange) where T<:Integer = convert(AbstractUnitRange{T}, A)
elconvert(::Type{T}, A::Tuple) where T = convert.(T, A)
elconvert(::Type{T}, A::Set{T}) where T = A
elconvert(::Type{T}, A::Set) where T = Set(convert.(T, A))
convert_eltype(::Type{T}, A::S) where {T,S} = convert(_to_eltype(T, S), A)
convert_eltype(::Type{T}, A::AbstractArray) where T = convert(AbstractArray{T}, A)
convert_eltype(::Type{T}, A::AbstractRange) where T = map(T, A)
convert_eltype(::Type{T}, A::AbstractUnitRange) where T<:Integer = convert(AbstractUnitRange{T}, A)
convert_eltype(::Type{T}, A::Tuple) where T = convert.(T, A)
convert_eltype(::Type{T}, A::Set{T}) where T = A
convert_eltype(::Type{T}, A::Set) where T = Set(convert.(T, A))

"""
_to_eltype(T, S)
Convert type `S` to have the `eltype` of `T`. See also [`elconvert`](@ref).
Convert type `S` to have the `eltype` of `T`. See also [`convert_eltype`](@ref).
"""
_to_eltype(::Type{T}, ::Type{S}) where {T,S} = eltype(S) == S ? T : eltype(S) == T ? S : MethodError(_to_eltype, T, S)
_to_eltype(::Type{T}, ::Type{<:AbstractArray{S,N}}) where {T,S,N} = AbstractArray{T,N}
Expand All @@ -74,7 +74,7 @@ for TYP in (Adjoint, Diagonal, Hermitian, Symmetric, SymTridiagonal, Transpose)
@eval _to_eltype(::Type{T}, ::Type{$TYP}) where T = $TYP{T}
@eval _to_eltype(::Type{T}, ::Type{$TYP{S}}) where {T,S} = $TYP{T}
@eval _to_eltype(::Type{T}, ::Type{$TYP{S,M}}) where {T,S,M} = $TYP{T,_to_eltype(T,M)}
@eval elconvert(::Type{T}, A::S) where {T,S<:$TYP} = convert(_to_eltype(T, S), A)
@eval convert_eltype(::Type{T}, A::S) where {T,S<:$TYP} = convert(_to_eltype(T, S), A)
end

@static if VERSION >= v"1.6"
Expand All @@ -86,7 +86,6 @@ _to_eltype(::Type{T}, ::Type{<:CartesianIndices}) where T = Array{T}

@static if VERSION >= v"1.7"
_to_eltype(::Type{T}, ::Type{<:StepRangeLen}) where T<:Real = StepRangeLen{T,_to_eltype(T,TwicePrecision),_to_eltype(T,TwicePrecision),Int}

else
_to_eltype(::Type{T}, ::Type{<:StepRangeLen}) where T<:Real = StepRangeLen{T,_to_eltype(T,TwicePrecision),_to_eltype(T,TwicePrecision)}
end
Expand Down Expand Up @@ -124,11 +123,11 @@ Convert type `S` to have the [`basetype`](@ref) of `T`.
_to_basetype(::Type{T}, ::Type{S}) where {T,S} = eltype(S) == S ? T : _to_eltype(_to_basetype(T, eltype(S)), S)

"""
baseconvert(T::Type, A)
convert_basetype(T::Type, A)
Similar to `convert(T, A)`, but `T` refers to the [`basetype`](@ref).
"""
baseconvert(::Type{T}, A::S) where {T,S} = convert(_to_basetype(T,S), A)
convert_basetype(::Type{T}, A::S) where {T,S} = convert(_to_basetype(T,S), A)

"""
precisiontype(T::Type)
Expand Down Expand Up @@ -172,30 +171,32 @@ _to_precisiontype(::Type{T}, ::Type{<:Rational}) where T<:Integer = Rational{T}
_to_precisiontype(::Type{T}, ::Type{S}) where {T,S} = eltype(S) == S ? T : _to_eltype(_to_precisiontype(T, eltype(S)), S)

"""
precisionconvert(T::Type, A, prec)
convert_precisiontype(T::Type, A, prec)
Convert `A` to have the [`precisiontype`](@ref) of `T`. `prec` is optional.
- When `T` has static precision (e.g. `Float64`), `prec` has no effect.
- When `T` has dynamic precision (e.g. `BigFloat`), `prec` specifies the precision of conversion. When `prec` is not provided, the precision is decided by the external setup from `T`.
- When `T` is an integer, the conversion will dig into `Rational` as well. In contrast, since `Rational` as a whole is more "precise" than an integer, [`precisiontype`](@ref) doesn't unwrap `Rational`.
# Examples
```jldoctest; setup = :(using EltypeExtensions: precisionconvert)
julia> precisionconvert(BigFloat, 1//3+im, 128)
```jldoctest; setup = :(using EltypeExtensions: convert_precisiontype)
julia> convert_precisiontype(BigFloat, 1//3+im, 128)
$(repr(bigfloatconvert(1//3, 128))) + 1.0im
julia> precisionconvert(Float16, [[m/n for n in 1:3] for m in 1:3])
julia> convert_precisiontype(Float16, [[m/n for n in 1:3] for m in 1:3])
3-element $(repr(Vector{Vector{Float16}})):
[1.0, 0.5, 0.3333]
[2.0, 1.0, 0.6665]
[3.0, 1.5, 1.0]
```
"""
precisionconvert(::Type{T}, A::S) where {T,S} = convert(_to_precisiontype(T,S), A)
precisionconvert(::Type{T}, A::S, prec) where {T,S} = precisionconvert(T, A)
precisionconvert(::Type{BigFloat}, A::S) where {S} = convert(_to_precisiontype(BigFloat,S), A)
precisionconvert(::Type{BigFloat}, x::Real, prec) = bigfloatconvert(x, prec)
precisionconvert(::Type{BigFloat}, x::Complex, prec) = Complex(bigfloatconvert(real(x), prec), bigfloatconvert(imag(x), prec))
precisionconvert(::Type{BigFloat}, A, prec) = precisionconvert.(BigFloat, A, prec)
convert_precisiontype(::Type{T}, A::S) where {T,S} = convert(_to_precisiontype(T,S), A)
convert_precisiontype(::Type{T}, A::S, prec) where {T,S} = convert_precisiontype(T, A)
convert_precisiontype(::Type{BigFloat}, A::S) where {S} = convert(_to_precisiontype(BigFloat,S), A)
convert_precisiontype(::Type{BigFloat}, x::Real, prec) = bigfloatconvert(x, prec)
convert_precisiontype(::Type{BigFloat}, x::Complex, prec) = Complex(bigfloatconvert(real(x), prec), bigfloatconvert(imag(x), prec))
convert_precisiontype(::Type{BigFloat}, A, prec) = convert_precisiontype.(BigFloat, A, prec)

include("deprecated.jl")

end
3 changes: 3 additions & 0 deletions src/deprecated.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@deprecate elconvert convert_eltype
@deprecate baseconvert convert_basetype
@deprecate precisionconvert convert_precisiontype
34 changes: 17 additions & 17 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ using Aqua
using LinearAlgebra

function testelconvert(T, A)
@test elconvert(T, A) isa _to_eltype(T, typeof(A))
@test convert_eltype(T, A) isa _to_eltype(T, typeof(A))
end

@testset "elconvert" begin
Expand Down Expand Up @@ -40,40 +40,40 @@ end

@testset "bugs" begin
@test _to_precisiontype(Float64, Complex) == Complex{Float64}
@test precisionconvert(BigFloat, rand(ComplexF64, 3)) isa Vector{Complex{BigFloat}}
@test convert_precisiontype(BigFloat, rand(ComplexF64, 3)) isa Vector{Complex{BigFloat}}

@testset "#7" begin
setprecision(256)
f(x) = precisionconvert(BigFloat, x, 256)
g(x) = precisionconvert(BigFloat, x)
f(x) = convert_precisiontype(BigFloat, x, 256)
g(x) = convert_precisiontype(BigFloat, x)
setprecision(128)
@test precision(f(π)) == 256 # static precision
@test precision(g(π)) == 128 # precision varies with the global setting
end

@testset "#8" begin
@test precisionconvert(Int128, Int8(1)//Int8(2)) isa Rational{Int128}
@test convert_precisiontype(Int128, Int8(1)//Int8(2)) isa Rational{Int128}
end

@testset "#10" begin
@test elconvert(Int32, 1:5) === Int32(1):Int32(5)
@test convert_eltype(Int32, 1:5) === Int32(1):Int32(5)
end
end

@testset "Misc" begin
@testset "Moved from DomainSets.jl" begin
@test elconvert(Float64, [1,2]) isa Vector{Float64}
@test elconvert(Float64, [1,2]) == [1,2]
@test elconvert(Float64, Set([1,2])) isa Set{Float64}
@test elconvert(Float64, Set([1,2])) == Set([1,2])
@test elconvert(Float64, 1:5) isa AbstractVector{Float64}
@test elconvert(Float64, 1:5) == 1:5
@test elconvert(Float64, 1) isa Float64
@test elconvert(Float64, 1) == 1
@test convert_eltype(Float64, [1,2]) isa Vector{Float64}
@test convert_eltype(Float64, [1,2]) == [1,2]
@test convert_eltype(Float64, Set([1,2])) isa Set{Float64}
@test convert_eltype(Float64, Set([1,2])) == Set([1,2])
@test convert_eltype(Float64, 1:5) isa AbstractVector{Float64}
@test convert_eltype(Float64, 1:5) == 1:5
@test convert_eltype(Float64, 1) isa Float64
@test convert_eltype(Float64, 1) == 1

@test elconvert(Float64, Set([1,2])) isa Set{Float64}
@test elconvert(Float64, (1,2)) isa NTuple{2,Float64}
@test elconvert(Int, (1,2)) == (1,2)
@test convert_eltype(Float64, Set([1,2])) isa Set{Float64}
@test convert_eltype(Float64, (1,2)) isa NTuple{2,Float64}
@test convert_eltype(Int, (1,2)) == (1,2)
end
end

Expand Down

0 comments on commit b16ba57

Please sign in to comment.