Shouldn't we have a List.nth in Elixir?

Erlang :list.nth simple, but 1 - based

nth(1, [H|_]) -> H;
nth(N, [_|T]) when N > 1 ->
    nth(N - 1, T).

Elixir Enum.atcool, but not focused on Lists

  def at(enumerable, index, default \\ nil) do
    case fetch(enumerable, index) do
      {:ok, h} -> h
      :error   -> default
    end
  end
 ...
 # other code to check if _is a list_ or not, before redirtecting to do_fetch...

Elixir could (should?) have List.nth, a 0-based equivalent of Erlang’s :list.nth that expects to work only on lists (nth could even make it into the kernel :slight_smile: )

The Enum,fetch/2 that Enum.at/2 uses has a clause that is specialised for lists, so it should be as efficient as the Erlang’s :lists.nth/2 without introducing extra functions.

https://github.com/elixir-lang/elixir/blob/master/lib/elixir/lib/enum.ex#L2773-L2778

3 Likes

Also nomenclature forbids zero basing nth. There is no element before the first.

1 Like

I don’t follow your argument

Well by writing List.nth(list, 0), you do request the element right before the first one.

There is never a zeroth item. You always start with the first. When one calls the function nth, he has to start counting with 1.

1 Like

Doesn’t Enum.at/2 contradict what you are saying?

Enum.at list, 0

It’s a language (any language english, polish, german, whatever) trait. There can not 0th element, how would you call it? Zeroth? You have to start count from 1 (one), so -> First, Second, Third, Fourth … Nth element. There is simply no Zeroth element :wink: It would be illogical.

On the other hand something can be at position marked as 0, zero. Something before it is at position 1 relative to position 0, and something behind is at position -1 (also relative to that 0 position). Or the other way round, based on do you want position number before or after that position. And element on position 1 is first from from, but that does not mean you’re “zeroth” from you.

Of course first element from the beginning of the list would be that at position 0, but then we would meant that we have to remember that nth element is on position n - 1. At that can lead to subtle off by 1 errors I would rather avoid.

In Erlang Unless otherwise stated, all functions assume that position numbering starts at 1 but in elixir we start at 0, hence having List.nth does not make, sadly, too much sense :frowning:

I disagree :slight_smile: 0th seems very logical if you think of it as a position. If that doesn’t make sense then how does nth make sense? You could even say Enum.at shows the nth element in a list where n is a position.

Can use a word to describe that position?

1 Like

Why add a new function which can be easily done in just the fashion that @michalmuskala outlines above?

1 Like

“0” is logical as an item offset when applied to a number of data type instances (of equal size), stored in a contiguous area of memory - what is typically thought of as an array.

In C this goes back to pointer arithmetic: arr[i] == *(arr + i) - this “index” syntax has been basically grandfathered into it’s various descendants and derivatives.

  • So item 1 is at offset 0 - or more generally
  • item n is at offset n-1 for n > 0

In functional programming languages “List” typically refers to a “Linked List”. In a linked list the items aren’t (usually) adjacent in memory so an offset isn’t as useful for “Lists”. Now Enum.at and Enum.fetch adopted the familiar 0-based index - in a sense that “index” is describing the “distance” into the enumeration starting at item 1 - i.e. item 1 is found at “distance” 0 from item 1 (rather than 0 being a “position”).

1 Like

The concept of a 0 indexed collection is the norm in programming languages. So, the concept of a 0th element does make sense in that way. Implementation wise things vary, you are right about addresses and the offsets in c. It is an implementation detail. When you talk about linked lists you can have a similar reasoning: How many nodes do you want to walk through to get the desired element? If you say 0 times, then you are referring to the head which is the first element.

1 Like

Yupp, it is the first, not the “zeroth”.

Entirely not helpful, but for some reason this discussion reminds me of

“Should array indices start at 0 or 1? My compromise of 0.5 was rejected without, I thought, proper consideration.” – Stan Kelly-Bootle

I think adding an nth function would cause undue confusion. Given a list of [1, 2, 3, 4] the item at position 0 is 1, but 1 is the first element in the list. List.first/1 and List.last/1 already satisfy a specific nth that lexically match how we think about them. List.nth(list, 4) does this grab the 4th item in the list, or the item at position 4?

1 Like

I have no idea why this discussion continues, and where is the confusion. I have no idea where do you expect it to lead by arguing about 0-based and 1-based indexation.

Elixir uses 0-based indexation everywhere. That’s it. There is no confusion what Enum.at(4) returns - the item with index 4, which is the fifth element of the collection.

As I stated in the first post, there’s entirely no reason to introduce List.nth/2 since this is completely covered by Enum.at/2. The performance is also exactly the same for both, as both use exactly the same code when executed on lists. It makes no point to introduce a 1-based list access when access everywhere in elixir, to all indexed structures is 0-based.

Please let’s stop this discussion if there’s nothing constructive to add.

6 Likes

I just happened upon an early Elixir video mentioned elsewhere on Elixir Forum where this very aspect of uniform indexing is mentioned as a feature at 18:08.

…where this is not supposed to be a feature but…

:laughing:

1 Like

As a bit of terminology, an index of a collection starts at 1, an offset into a collection starts at 0. Most languages use offset (regardless of what word they use). :slight_smile:

1 Like