Why `front` and `tail` are defined for only `Tuple`?


I expect below, but MethodError raised:

julia> x = [3, 5, 7, 4, 0, 9];
julia> Base.front(x)

Of course x[Not(end)] is also great choice if we are using DataFrames.jl, or just x[(end-1):end] is OK. But maybe front and tail are very useful like first and last. Is there any reason why the functions are not defined for other collections, for instance, performance issue?

I don’t know about historical reasons, but generalizing this is a little tough. For tuples, they are not allocating - the compiler knows exactly the type of Tuple it will return based on the argument. But to do this for a Vector, you’d either need to make a new container or return a surprise View.

Edit: one other option is a mutation with pop! or popfirst! … But we already have those functions :sweat_smile:

first and last apply generically to iterators. Base.front is basically the same as first, but Base.tail might not work as expected for all iterators.

1 Like

Be very careful with this Not thing, assuming it comes from InvertedIndices.jl. Its implementation is very inefficient, compare:

# manual indexing, baseline:
julia> @btime $x[begin:end-1]
  109.519 ns (1 allocation: 96 bytes)

# Not(): 30x slower!
julia> using InvertedIndices
julia> @btime $x[Not(end)]
  2.961 μs (35 allocations: 1.14 KiB)

# there's a readable zero-overhead solution in Accessors:
# @delete x[end]
# or
# @delete last(x)
julia> using Accessors
julia> @btime @delete $x[end]
  95.899 ns (1 allocation: 112 bytes)
julia> @btime @delete last($x)
  95.795 ns (1 allocation: 112 bytes)

But yeah, anyway a more general front/tail in Base would be nice.

1 Like

That last optic seems sorta crazy. What is happening there?

Wow, good to know! Thank you!! But Not is very convenient :joy:… I have a question, do your alternative solution actually changes original x?

I’ve only seen these methods used commonly for recursive inlined Tuple iteration. If comprehension was type stable for tuples it’d probably be used instead.

No magic, just https://github.com/JuliaObjects/Accessors.jl/blob/master/src/functionlenses.jl#L6 (:

Well, Accessors are also very convenient — but more general and efficient!

No, all Accessors operations create a new object and keep the original one intact.
So, it’s used as

y = @delete last(x)

and not as

@delete last(x)