[ANN] OneTwoMany.jl

I’ve put together a tiny package OneTwoMany.jl - that provides a function getsecond, as well as functions first_and_rest and first_second_rest.

Julia provides the functions first and last , but not second (except for Dates.second , which is about seconds in time). last can be used to access the second element of a Tuple with two elements or a Pair , but getsecond is clearer semantically. With tuples, it is also safer in cases where the upstream code that generates the tuple might change and generate longer tuples in the future.

The package is currently being registered. I couldn’t find a getsecond in any central lightweight package, but if this duplicates something similar, please let me know before registration is complete. :slight_smile:

3 Likes

Cool package! Any reason not to have getthird, getforth, etc?

I was wondering how you’d handle strings and your

getsecond(str::AbstractString) = str[begin+1]

seems potentially a bit dangerous:

julia> s = "∀∃α"; getsecond(s)
ERROR: StringIndexError: invalid index [2], valid nearby indices [1]=>'∀', [4]=>'∃'
Stacktrace:
 [1] string_index_err(s::String, i::Int64)
   @ Base ./strings/string.jl:12
 [2] getindex_continued(s::String, i::Int64, u::UInt32)
   @ Base ./strings/string.jl:248
 [3] getindex
   @ ./strings/string.jl:241 [inlined]
 [4] getsecond(str::String)
   @ Main ./REPL[9]:1
 [5] top-level scope
   @ REPL[10]:1

I’d suggest using str[nextind(str, 1)]:

julia> getsecond(str::AbstractString) = str[nextind(str, 1)];

julia> getsecond(s)
'∃': Unicode U+2203 (category Sm: Symbol, math)

(as would probably be expected I imagine)

6 Likes

You can use

nth(itr, n) = first(Iterators.drop(itr, n-1))

to get the n-th element of an iterator.

16 Likes

I’d say the first element of a collection is the one that is requested directly most often, if only a single element is accessed. Then the second one. After that, in my personal experience, direct access to only one element is less common. One doesn’t see x[3] or x[4] that much in code, compared to x[1] and x[2].

1 Like

I always forget about Iterators… if you use that solution you can remove the special casing on AbstractString:

julia> s = "∀∃α"
"∀∃α"

julia> nth(itr, n) = first(Iterators.drop(itr, n-1))
nth (generic function with 1 method)

julia> nth(s, 2)
'∃': Unicode U+2203 (category Sm: Symbol, math)

You make a good point there @tlienart.

I’m a bit torn about this. On the one hand, users should expect getsecond(x) to do the same as x[2] (for collections with one-based indexing). On the other hand, retrieving whole unicode characters from strings is certainly more useful.

I agree, we should return the second element from a viewpoint of iteration for strings, not getindex(s,2).

2 Likes

Totally, but all of these index accessing functions are just niceties. You could define your own first after all!

I agree it’s less than common, but given that Haskell has first through ninth before switching to nth, at least some people find it useful.

Anyway, I’m not here to debate the line where having a name for an index access function becomes useful. I was just curious :smile:

If people have use cases for getthird, getfourth and so on, the package is certainly open for expansion (as long as it stays very lightweight).

1 Like

Maybe, also worth to extend Base.tail() to more types? Then I guess this function won’t be necessary (:

2 Likes
help?> Iterators.peel
  peel(iter)


  Returns the first element and an iterator over the remaining elements.

  If the iterator is empty return nothing (like iterate).

  │ Julia 1.7
  │
  │  Prior versions throw a BoundsError if the iterator is empty.

  See also: Iterators.drop, Iterators.take.

  Examples
  ≡≡≡≡≡≡≡≡≡≡

  julia> (a, rest) = Iterators.peel("abc");
  
  julia> a
  'a': ASCII/Unicode U+0061 (category Ll: Letter, lowercase)
  
  julia> collect(rest)
  2-element Vector{Char}:
   'b': ASCII/Unicode U+0062 (category Ll: Letter, lowercase)
   'c': ASCII/Unicode U+0063 (category Ll: Letter, lowercase)
1 Like

Thanks @jar1 - good point, I should use that to implement first_and_rest and possiblity also first_second_rest for iterators.

Ah, the times I’ve wished for a generic, offical and exported tail in Julia. :slight_smile:

Base.tail is in the julia docs, doesn’t it mean the function is as official as it gets?
As for “generic”, maybe all it takes is someone making a PR to Julia extending tail to more types?

1 Like

Oh, you’re right, it is documented now (didn’t use to be in the “olden” days).

I’ve updated OneTwoMany with the improved string handling suggested by @tlienart (thanks!).

1 Like

I’ll keep the current implementation for now - it throws an exception for empty iterators, while peel returns nothing.