Skip to content

Commit 91a8d10

Browse files
authored
simplify (#19)
1 parent f948298 commit 91a8d10

File tree

2 files changed

+53
-120
lines changed

2 files changed

+53
-120
lines changed

src/CheckedSizeProduct.jl

Lines changed: 26 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,30 @@
11
module CheckedSizeProduct
22
export checked_size_product
33

4-
"""
5-
StatusInvalidValue::DataType
6-
7-
Singleton type. Returned by [`checked_size_product`](@ref) in case its input
8-
contained an invalid value. An invalid value is either:
9-
* less than zero
10-
* equal to `typemax(T)`
11-
12-
See also: [`StatusOverflow`](@ref)
13-
"""
14-
struct StatusInvalidValue end
15-
16-
"""
17-
StatusOverflow::DataType
18-
19-
Singleton type. Returned by [`checked_size_product`](@ref) in case the product
20-
is not representable in the element type of the input.
21-
22-
See also: [`StatusInvalidValue`](@ref)
23-
"""
24-
struct StatusOverflow end
25-
264
"""
275
checked_size_product(size_tuple)
286
297
In short; a safe product, suitable for computing, e.g., the value of
308
`length(dense_array)` from the value of `size(dense_array)`. In case everything
319
is as expected, return the value of the product of the elements of the given
32-
tuple. Otherwise, return a `NamedTuple` containing Boolean values with
33-
information on the encountered problems.
10+
tuple. Otherwise, return `nothing`.
11+
12+
In more detail; given `size_tuple`, a nonempty homogeneous tuple of `Integer`-likes:
13+
14+
0. Give the name `T` to `eltype(size_tuple)` for the purposes of this doc string.
3415
35-
In more detail; given `size_tuple`, a nonempty homogeneous tuple of
36-
`Integer`-likes:
37-
0. Give the name `T` to `eltype(size_tuple)` for the purposes of this doc
38-
string.
3916
1. The user must ensure `T` supports:
17+
4018
* `Base.Checked.mul_with_overflow`
4119
* `iszero`
4220
* `typemax`
4321
* `<`
4422
* `==`
45-
2. Calculate the product. In case no element is negative, no element is
46-
`typemax(T)` and the product is representable as `T`, return the product.
47-
Otherwise return:
48-
* a value of type [`StatusInvalidValue`]: the input contains a negative
49-
element, or an element equal to `typemax(T)`
50-
* a value of type [`StatusOverflow`]: the product is not representable as
51-
`T`
5223
53-
Throws when given an empty tuple to avoid having to choose a default return
54-
type arbitrarily.
24+
2. Calculate the product. In case no element is negative, no element is `typemax(T)`
25+
and the product does not overflow, return the product. Otherwise return `nothing`.
26+
27+
Throws if `isempty(size_tuple)` to avoid having to choose a return type arbitrarily.
5528
"""
5629
function checked_size_product end
5730

@@ -65,75 +38,44 @@ module CheckedSizeProduct
6538
end
6639
end
6740

68-
@assume_terminates_locally function checked_dims_impl(t::NonemptyNTuple)
41+
@assume_terminates_locally function checked_dims(t::NonemptyNTuple)
6942
a = first(t)
43+
tl = Base.tail(t)
7044
have_overflow = false
71-
for i eachindex(t)[2:end]
72-
b = t[i]
45+
for b tl
7346
(m, o) = Base.Checked.mul_with_overflow(a, b)
7447
a = m
7548
have_overflow |= o
7649
end
7750
(a, have_overflow)
7851
end
7952

80-
@assume_terminates_locally function any_impl(f, t::NTuple)
81-
a = false
82-
for i eachindex(t)
83-
e = t[i]
84-
b = f(e)::Bool # assuming no `missing`
85-
a |= b
86-
end
87-
a
88-
end
89-
90-
# define singleton types for these functions to be able to dispatch on them
9153
function is_negative(x)
92-
x < 0
54+
x < typeof(x)(0)
9355
end
56+
9457
function is_typemax(x)
58+
if x isa BigInt
59+
return false
60+
end
9561
x == typemax(x)
9662
end
9763

98-
const HasGoodEffects = Union{
99-
Int8, Int16, Int32, Int64, Int128,
100-
}
101-
102-
function checked_dims(t::NonemptyNTuple)
103-
checked_dims_impl(t)
104-
end
105-
function any_(f, t::NonemptyNTuple)
106-
any_impl(f, t)
107-
end
108-
@static if isdefined(Base, Symbol("@assume_effects"))
109-
Base.@assume_effects :nothrow function checked_dims(
110-
t::(NonemptyNTuple{T} where {T <: HasGoodEffects}),
111-
)
112-
checked_dims_impl(t)
113-
end
114-
Base.@assume_effects :nothrow function any_(
115-
f::Union{typeof(iszero), typeof(is_negative), typeof(is_typemax)},
116-
t::(NonemptyNTuple{T} where {T <: HasGoodEffects}),
117-
)
118-
any_impl(f, t)
119-
end
64+
function typeassert_bool(x::Bool)
65+
x
12066
end
12167

12268
function checked_size_product(t::NonemptyNTuple)
123-
any_is_zero = any_(iszero, t)
124-
any_is_negative = any_(is_negative, t)
125-
any_is_typemax = any_(is_typemax, t)
69+
any_is_zero = any(typeassert_bool iszero, t)
70+
any_is_negative = any(typeassert_bool is_negative, t)
71+
any_is_typemax = any(typeassert_bool is_typemax, t)
12672
(product, have_overflow) = checked_dims(t)
12773
any_is_invalid = any_is_negative | any_is_typemax
12874
is_not_representable = have_overflow & !any_is_zero
129-
if !(any_is_invalid | is_not_representable)
130-
product
75+
if any_is_invalid | is_not_representable
76+
nothing
13177
else
132-
if any_is_invalid
133-
StatusInvalidValue()
134-
else
135-
StatusOverflow()
136-
end
78+
product
13779
end
13880
end
13981
end

test/runtests.jl

Lines changed: 27 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using CheckedSizeProduct
22
using Test
3-
using Aqua: Aqua
43

54
module ExampleInts
65
export ExampleInt
@@ -20,46 +19,33 @@ module ExampleInts
2019
(p, f) = Base.Checked.mul_with_overflow(l.v, r.v)
2120
(ExampleInt(p), f)
2221
end
23-
function Base.:(<)(l::ExampleInt, r::Int)
24-
l.v < r
25-
end
26-
function Base.promote_rule(::Type{Int}, ::Type{ExampleInt})
27-
ExampleInt
28-
end
29-
function Base.promote_rule(::Type{ExampleInt}, ::Type{Int})
30-
ExampleInt
31-
end
32-
function Base.promote_rule(::Type{Bool}, ::Type{ExampleInt})
33-
ExampleInt
34-
end
35-
function Base.promote_rule(::Type{ExampleInt}, ::Type{Bool})
36-
ExampleInt
22+
function Base.:(<)(l::ExampleInt, r::ExampleInt)
23+
l.v < r.v
3724
end
3825
function ExampleInt(n)
26+
if n isa ExampleInt
27+
return n
28+
end
3929
ExampleInt(Int(n)::Int)
4030
end
4131
function Base.convert(::Type{ExampleInt}, n)
4232
ExampleInt(n)
4333
end
44-
function Base.convert(::Type{ExampleInt}, n::ExampleInt)
45-
n
46-
end
4734
end
4835

4936
using .ExampleInts: ExampleInt
5037

38+
const eltypes = (Int8, Int16, Int32, Int64, Int128, ExampleInt, BigInt)
39+
5140
@testset "CheckedSizeProduct.jl" begin
52-
@testset "Code quality (Aqua.jl)" begin
53-
Aqua.test_all(CheckedSizeProduct)
54-
end
5541
@testset "empty input" begin
56-
@test_throws Exception checked_size_product(())
42+
@test_throws MethodError checked_size_product(())
5743
end
5844
@testset "heterogeneous input" begin
59-
@test_throws Exception checked_size_product((Int32(2), Int64(3)))
45+
@test_throws MethodError checked_size_product((Int32(2), Int64(3)))
6046
end
6147
@testset "singleton input" begin
62-
for T (Int8, Int16, Int32, Int64, Int128, ExampleInt)
48+
for T eltypes
6349
for x 0:100
6450
y = T(x)
6551
@test y === checked_size_product((y,))
@@ -72,9 +58,9 @@ using .ExampleInts: ExampleInt
7258
(-1, typemax(Int)), (typemax(Int), -1),
7359
(0, -4, -4), (-4, 1, 0), (-4, -4, 1),
7460
)
75-
@test checked_size_product(t) isa CheckedSizeProduct.StatusInvalidValue
61+
@test nothing === checked_size_product(t)
7662
s = map(ExampleInt, t)
77-
@test checked_size_product(s) isa CheckedSizeProduct.StatusInvalidValue
63+
@test nothing === checked_size_product(s)
7864
end
7965
end
8066
@testset "input includes `typemax(T)`" begin
@@ -89,9 +75,9 @@ using .ExampleInts: ExampleInt
8975
for t (
9076
(m,), (m, m), (1, m), (m, 1), (0, m), (m, 0), (-1, m), (m, -1),
9177
)
92-
@test checked_size_product(t) isa CheckedSizeProduct.StatusInvalidValue
78+
@test nothing === checked_size_product(t)
9379
s = map(ExampleInt, t)
94-
@test checked_size_product(s) isa CheckedSizeProduct.StatusInvalidValue
80+
@test nothing === checked_size_product(s)
9581
end
9682
end
9783
@testset "overflows" begin
@@ -101,9 +87,9 @@ using .ExampleInts: ExampleInt
10187
(m, m), (15, m), (m, 15), (m, m, m), (1, m, m), (m, 1, m), (m, m, 1),
10288
(b, b), (1, b, b),
10389
)
104-
@test checked_size_product(t) isa CheckedSizeProduct.StatusOverflow
90+
@test nothing === checked_size_product(t)
10591
s = map(ExampleInt, t)
106-
@test checked_size_product(s) isa CheckedSizeProduct.StatusOverflow
92+
@test nothing === checked_size_product(s)
10793
end
10894
end
10995
@testset "overflows, but OK because of multiplication with zero" begin
@@ -123,20 +109,25 @@ using .ExampleInts: ExampleInt
123109
for x ran
124110
for y ran
125111
for z ran
126-
for T (Int8, Int16, Int32, Int64, Int128, ExampleInt)
112+
for T eltypes
127113
ref = T(prod((x, y, z)))
128114
o = T(1)
129115
a = T(x)
130116
b = T(y)
131117
c = T(z)
132-
@test ref === checked_size_product((a, b, c))
133-
@test ref === checked_size_product((o, a, b, c))
134-
@test ref === checked_size_product((a, o, b, c))
135-
@test ref === checked_size_product((a, b, o, c))
136-
@test ref === checked_size_product((a, b, c, o))
118+
@test ref == checked_size_product((a, b, c))::T
119+
@test ref == checked_size_product((o, a, b, c))::T
120+
@test ref == checked_size_product((a, o, b, c))::T
121+
@test ref == checked_size_product((a, b, o, c))::T
122+
@test ref == checked_size_product((a, b, c, o))::T
137123
end
138124
end
139125
end
140126
end
141127
end
142128
end
129+
130+
using Aqua: Aqua
131+
@testset "Code quality (Aqua.jl)" begin
132+
Aqua.test_all(CheckedSizeProduct)
133+
end

0 commit comments

Comments
 (0)