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

Add only function #25078

Closed
wants to merge 1 commit into from
Closed

Add only function #25078

wants to merge 1 commit into from

Conversation

andyferris
Copy link
Member

@andyferris andyferris commented Dec 14, 2017

The function only(x) returns the one-and-only element of a collection x, or else throws an error.

I discovered this little gem in LINQ (under the IEnumerable method x.Single()), and instantly fell in love. In particular, it's quite useful when you have e.g. filtered down some data and expect only one element, but it feels dirty to call x[1] or first(x) without at least checking that it has one (and only one) element. This little function lets you grab the element and make the assertion that it is the only element, all in a few characters.

(Note: this is currently a part of my SplitApplyCombine.jl experiment, which has strategies to deal with nested containers, but to me seems a much better fit for Base / Base.Iterators, which deals with... iteration...)

The function `only(x)` returns the one-and-only element of a collection
`x`, or else throws an error.
@StefanKarpinski
Copy link
Member

Handy, but do note it's a feature so it could be added at any time.

Copy link
Member

@vtjnash vtjnash left a comment

Choose a reason for hiding this comment

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

I think this would be nice, for the expressive power for anyone reading the code (for places where currently we use first or [1], as Andy mentioned. For example, dot = only([2 3] * [1; 2]))

Returns the one and only element of collection `x`, and throws an error if the collection
has zero or multiple elements.
"""
Base.@propagate_inbounds function only(x)
Copy link
Member

Choose a reason for hiding this comment

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

It seems odd to mark this this way, where @inbounds only(x) is first(x)

Copy link
Member Author

Choose a reason for hiding this comment

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

The oddness here comes because I want @inbounds to propagate to next, since that can have @boundschecks in it.

@StefanKarpinski
Copy link
Member

StefanKarpinski commented Dec 14, 2017

Also, isn't only(x) effectively an alias for x[]? I'm fairly certain that does the same thing now. Edit: or at least it will once we make "generalized linear indexing" a hard error in 1.0:

julia> x = [1]
1-element Array{Int64,1}:
 1

julia> x[]
1

julia> push!(x, 2)
2-element Array{Int64,1}:
 1
 2

julia> x[]
WARNING: omitting indices for non-singleton trailing dimensions is deprecated. Add `1` as trailing indices or use `reshape` to make the dimensionality of the array match the number of indices.
Stacktrace:
[...]
1

@andyferris
Copy link
Member Author

isn't only(x) effectively an alias for x[]

Only for arrays, this is for any iterable.

@StefanKarpinski
Copy link
Member

We could make it work for any iterable, using this definition 😀

@andyferris
Copy link
Member Author

andyferris commented Dec 15, 2017

You mean using x[] instead of only(x), globally?

I guess, I definitely had thought of that, but had decided against it for now. Personally, I'd like to avoid conflating the iteration API with the indexing API any more than necessary, until this has been more thoroughly thought through. The relationship between iterable containers and indexable containers is already challenging - for example, with a one-element d::Dict I would expect only(d) to be a pair and d[] to be a value.

(If we were to link indexing and iteration more strongly in the future, we could revisit this).

@andyferris
Copy link
Member Author

Going the other way - I'd have something like getindex(x::Any) = getindex(x, only(keys(x)) 😀

(It makes sense, since then x[] works for any indexable with one key/index, but doesn't extend any (real) meaning to getindex to non-indexable iterables).

@jw3126
Copy link
Contributor

jw3126 commented Feb 21, 2018

Is there intend to include this feature into Base at some point? I implemented the quick and dirty variant

@assert length(iter) == 1; first(iter)

dozens of times in different places. So at least for me this would be very useful. See also here.

@andyferris
Copy link
Member Author

I'm happy to rebase this.

There remains some TODOs:

  • Bikeshed name: only, single (LINQ), possibly others
  • A version with a default value in case of being empty could be useful - working a bit like get but always for the single element of arbitrary iterables.

@jw3126
Copy link
Contributor

jw3126 commented Feb 22, 2018

The default for empty version idea sounds good. I also used quick and dirty variants of it a few times. For the name, at least single has some tradition, not sure about only? I don't really have a preference though.

@tpapp
Copy link
Contributor

tpapp commented Feb 22, 2018

I suggest singleton.

@pkofod
Copy link
Contributor

pkofod commented Feb 22, 2018

I suggest singleton.

A singleton is a set with one element though, and this will not return that, this will return the element of a "singleton" (although not only a Set of course).

@oxinabox
Copy link
Contributor

Bikeshed name: only, single (LINQ), possibly others

only is good.

A version with a default value in case of being empty could be useful - working a bit like get but always for the single element of arbitrary iterables.

Nah, lets not over-complicate things. The usecase of: "I am fairly sure i have narrowed this down to 1 thing" is very clear.
The usecase, of "I am fairly sure i have narrowed this down to 0 or 1 things" is much less.
first doesn't take a default, so I think only should not either.
It is more of an assertion than anything else.
If you want logic then the y = isempty(xs) ? default : only(xs) is just fine.

@KristofferC
Copy link
Member

@goretkin
Copy link
Contributor

Just an observation: in Python, there's an idiom to assign the first element, and assert it's the only one.

In [1]: (x,) = [3]

In [2]: (x,) = [3,4]
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-2-b3aec10ab953> in <module>()
----> 1 (x,) = [3,4]

ValueError: too many values to unpack (expected 1)

This doesn't work in Julia, however

julia> (x, ) = [3]
1-element Array{Int64,1}:
 3

julia> (x, ) = [3,4]
2-element Array{Int64,1}:
 3
 4

julia> x
3

@nickrobinson251
Copy link
Contributor

What is the status of this?

I'd find this feature useful :)

@oxinabox
Copy link
Contributor

@nickrobinson251 probably best to just start a new PR since htis is so far behind head.

@StefanKarpinski
Copy link
Member

Seems like a useful feature. I would like to generalize it to only(itr, n=1) which iterates the first n values and asserts that there are no more with one being the default number to take.

@oxinabox
Copy link
Contributor

Seems like a useful feature. I would like to generalize it to only(itr, n=1) which iterates the first n values and asserts that there are no more with one being the default number to take.

What would it return?
only is (in .Net) the same as first but with the assertion that there is only one element.
So it returns the element in the iterator.

for what you desribe, I can only assume it would return an iterator.
And in tht case I think I would rather call it takeonly
Or something.
And I also think it is kinda obsure and not worth having in Base?

@andyferris
Copy link
Member Author

Yes, recall it returns a single value, not a collection (it’s for unwrapping collections containing only one thing).

@tkf
Copy link
Member

tkf commented Aug 31, 2019

This can also be expressed as something(...x) based on the feature discussed #32860. (edit: never mind; this doesn't work)

@nickrobinson251
Copy link
Contributor

Added in #33129 🎉

@rfourquet rfourquet deleted the ajf/only branch November 2, 2019 09:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.