Skip to content
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

WIP: Add only function #3

Closed
wants to merge 6 commits into from
Closed
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
3 changes: 2 additions & 1 deletion NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@ Standard library changes

* The methods of `mktemp` and `mktempdir` which take a function body to pass temporary paths to no longer throw errors if the path is already deleted when the function body returns ([#33091]).

#### Libdl
* New function `only(x)` returns the one-and-only element of a collection `x`, and throws an error if `x` contains zero or multiple elements.

#### Libdl

#### LinearAlgebra

Expand Down
2 changes: 1 addition & 1 deletion base/Base.jl
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ include("ntuple.jl")
include("abstractdict.jl")

include("iterators.jl")
using .Iterators: zip, enumerate
using .Iterators: zip, enumerate, only
using .Iterators: Flatten, Filter, product # for generators

include("namedtuple.jl")
Expand Down
1 change: 1 addition & 0 deletions base/exports.jl
Original file line number Diff line number Diff line change
Expand Up @@ -628,6 +628,7 @@ export

enumerate, # re-exported from Iterators
zip,
only,

# object identity and equality
copy,
Expand Down
36 changes: 34 additions & 2 deletions base/iterators.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ using .Base:
@inline, Pair, AbstractDict, IndexLinear, IndexCartesian, IndexStyle, AbstractVector, Vector,
tail, tuple_type_head, tuple_type_tail, tuple_type_cons, SizeUnknown, HasLength, HasShape,
IsInfinite, EltypeUnknown, HasEltype, OneTo, @propagate_inbounds, Generator, AbstractRange,
LinearIndices, (:), |, +, -, !==, !, <=, <, missing, map, any
LinearIndices, (:), |, +, -, !==, !, <=, <, missing, map, any, @boundscheck, @inbounds

import .Base:
first, last,
Expand Down Expand Up @@ -929,7 +929,6 @@ julia> collect(Iterators.partition([1,2,3,4,5], 2))
"""
partition(c::T, n::Integer) where {T} = PartitionIterator{T}(c, Int(n))


struct PartitionIterator{T}
c::T
n::Int
Expand Down Expand Up @@ -1095,4 +1094,37 @@ eltype(::Type{Stateful{T, VS}} where VS) where {T} = eltype(T)
IteratorEltype(::Type{Stateful{T,VS}}) where {T,VS} = IteratorEltype(T)
length(s::Stateful) = length(s.itr) - s.taken

"""
only(x)

Returns the one and only element of collection `x`, and throws an `ArgumentError` if the
collection has zero or multiple elements.
"""
@propagate_inbounds function only(x)
i = iterate(x)
@boundscheck if i === nothing
throw(ArgumentError("Collection is empty, must contain exactly 1 element"))
end
(ret, state) = i
@boundscheck if iterate(x, state) !== nothing
throw(ArgumentError("Collection has multiple elements, must contain exactly 1 element"))
end
return ret
end

# Collections of known size
nickrobinson251 marked this conversation as resolved.
Show resolved Hide resolved
only(x::Ref) = x[]
only(x::Number) = x
only(x::Char) = x
only(x::Tuple{}) = throw(ArgumentError("Tuple is empty, must contain exactly 1 element"))
only(x::Tuple{Any}) = x[1]
oxinabox marked this conversation as resolved.
Show resolved Hide resolved
only(x::Tuple) = throw(
ArgumentError("Tuple contains $(length(x)) elements, must contain exactly 1 element")
)
only(a::AbstractArray{<:Any, 0}) = @inbounds return a[]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it not possible to construct a zero length AbstractArray{<:Any, 0} ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do I test that? i.e. How does one create a zero-dim array of any length?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

julia> collect(1)
0-dimensional Array{Int64,0}:
1

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Idk how to create an empty one

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not know the answer to this (and Andy Ferris is the wise fellow who actually wrote this)

only(x::NamedTuple{<:Any, <:Tuple{Any}}) = first(x)
only(x::NamedTuple) = throw(
ArgumentError("NamedTuple contains $(length(x)) elements, must contain exactly 1 element")
)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure of style in Base, but this fits with other files as far as I can tell...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am tempted to say this should be a BoundsError or a DimensionMismatch ?

Copy link
Author

@nickrobinson251 nickrobinson251 Aug 31, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • yeah, I can see the case for BoundsError as the error type... but on the other hand the message doesn't really feel right
julia> x = [1, 2];

julia> throw(BoundsError(x, 2))
ERROR: BoundsError: attempt to access 2-element Array{Int64,1} at index [2]
  • Similarly, with DimensionMismatch, what is the "mismatch"?

  • ArgumentError feels better to me... but i'd certainly like to hear other opinions!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fair


end
1 change: 1 addition & 0 deletions doc/src/base/iterators.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ Base.Iterators.flatten
Base.Iterators.partition
Base.Iterators.filter
Base.Iterators.reverse
Base.Iterators.only
```
34 changes: 32 additions & 2 deletions test/iterators.jl
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,6 @@ end
@test Base.IteratorEltype(repeated(0, 5)) == Base.HasEltype()
@test Base.IteratorSize(zip(repeated(0), repeated(0))) == Base.IsInfinite()


# product
# -------

Expand Down Expand Up @@ -411,7 +410,6 @@ for n in [5,6]
[(1,1),(2,2),(3,3),(4,4),(5,5)]
end


@test join(map(x->string(x...), partition("Hello World!", 5)), "|") ==
"Hello| Worl|d!"

Expand Down Expand Up @@ -647,3 +645,35 @@ end
@test length(collect(d)) == 2
@test length(collect(d)) == 0
end

@testset "only" begin
nickrobinson251 marked this conversation as resolved.
Show resolved Hide resolved
@test only([3]) === 3
@test_throws ArgumentError only([])
@test_throws ArgumentError only([3, 2])

@test @inferred(only((3,))) === 3
@test_throws ArgumentError only(())
@test_throws ArgumentError only((3, 2))

@test only(Dict(1=>3)) === (1=>3)
@test_throws ArgumentError only(Dict{Int,Int}())
@test_throws ArgumentError only(Dict(1=>3, 2=>2))

@test only(Set([3])) === 3
@test_throws ArgumentError only(Set(Int[]))
@test_throws ArgumentError only(Set([3,2]))

@test @inferred(only((;a=1))) === 1
@test_throws ArgumentError only(NamedTuple())
@test_throws ArgumentError only((a=3, b=2.0))

@test @inferred(only(1)) === 1
@test @inferred(only('a')) === 'a'
@test @inferred(only(Ref([1, 2]))) == [1, 2]

@test only(1 for ii in 1:1) === 1
@test only(1 for ii in 1:10 if ii < 2) === 1
@test_throws ArgumentError only(1 for ii in 1:10)
@test_throws ArgumentError only(1 for ii in 1:10 if ii > 2)
@test_throws ArgumentError only(1 for ii in 1:10 if ii > 200)
end