17

Enumerable has first:

(3..5).to_enum.first
# => 3

but it does not have last:

(3..5).to_enum.last
# => NoMethodError: undefined method `last' for #<Enumerator: 3..5:each>

Why is that?

sawa
  • 165,429
  • 45
  • 277
  • 381

4 Answers4

15

It is because not all enumerable objects have the last element.

The simplest example would be:

[1, 2, 3].cycle

# (an example of what cycle does)
[1,2,3].cycle.first(9) #=> [1, 2, 3, 1, 2, 3, 1, 2, 3]

Even if the enumerator elements are finite, there is no easy way to get the last element other than iterating through it to the end, which would be extremely inefficient.

sandstrom
  • 14,554
  • 7
  • 65
  • 62
BroiSatse
  • 44,031
  • 8
  • 61
  • 86
  • 5
    Doesn't explain why `Enumerable` defines e.g. `map` or `max`, won't work with `[1].cycle` too. Why `last` is special? – Victor Moroz Mar 20 '14 at 02:30
  • @VictorMoroz - It is special as it would work in most of the cases, but it would be inefficient and in some cases might actually break the enumerator (For example iterating through IO objects). It seems like ruby creators thought about adding this method, however decided that it would be broadly misused. – BroiSatse Mar 20 '14 at 02:35
  • 1
    Simplest example is actually `loop`. – Marc-André Lafortune Mar 20 '14 at 04:55
  • 2
    Doesn't seem like a great reason. Infinite elements break many popular `Enumeable` methods, like `#to_a`, `#min`, `#all?`, and `#reduce`. – Matthew Feb 26 '21 at 17:58
6

Because not all Enumerable has last element, and this may or may not because that the Enumerable contains no element.

Consider the following Enumerable:

a = Enumerator.new do |yielder|
  while true
    yielder << 1
  end
end

It's a infinite Enumerable.

Enumerable is a mechanism to iterate a sequence of elements. For some of the iterate process, this may only perform once. In order to get the last element (if there is actually one), it must evaluate the whole iterate process and get the last one. After that, the Enumerable is invalid.

Arie Xiao
  • 13,909
  • 3
  • 31
  • 30
2

The only reason I can think of is Enumerables may be infinite streams.

infinity = Float::INFINITY
range = 1..infinity

range.to_enum.first
# => 1

range.to_a.last # will never finish
Matheus Moreira
  • 17,106
  • 3
  • 68
  • 107
  • I don't think this is the reason because `Float::INFINITY` can appear as the beginning element, in which case `first` would raise an error rather than being undefined: `(Float::INFINITY..5).to_enum => #`. But `(Float::INFINITY..5).to_enum.first # => TypeError: can't iterate from Float`. – sawa Mar 20 '14 at 02:11
  • @sawa, that's a problem with the range I used to illustrate my point, not with the concept of infinite streams. – Matheus Moreira Mar 20 '14 at 02:14
-1

I do not agree with opinion that not all Enumerable has last element. I think few Enumerator methods are unable to end the loop while responding to to_a method. That is why Enumerable knows first element for sure but they can not determine about its last element.

Enumerator#each
each_enum = (0..1).each
#=> #<Enumerator: 0..1:each> 
each_enum.to_a
#=> [0, 1] 

Whereas

Enumerator#cycle
cycle_enum = (0..1).cycle
#=> #<Enumerator: 0..1:cycle> 
cycle_enum.to_a

keeps on pushing next element into array resulting in infinite loop.

See this link(cycle) and this link(each) for few code execution to observe what I want to say.

Alok Anand
  • 3,346
  • 1
  • 20
  • 17
  • 1
    "I do not agree with opinion that not all Enumerable has last element" - So, in this case, what is the last element of `cycle_enum`? – Sergio Tulentsev Aug 31 '16 at 15:14