4

I have a lazy evaluation, where I want the first truthy result resulting from a map operation, and once again I found myself writing .find { |e| e } on the end of my expression.

Here's a simple example; the array and map block are, of course, different in my real life:

[nil, 2, 3, 4].lazy.map{ |e| e }.find { |e| e }

I'm always a little surprised/disappointed when I have to add the block { |e| e } to a select or find, especially if it's a lazy evaluation, because both - redundantly - seem to be identity functions by default:

> [nil, 2, 3, 4].find { |e| e } 
 => 2
> [nil, 2, 3, 4].find
 => #<Enumerator: [nil, 2, 3, 4]:find>
> [nil, 2, 3, 4].find.map { |e| e }
 => [nil, 2, 3, 4] 

Does this Enumerator practically differ at all from the one obtained from .each?

> [nil, 2, 3, 4].each.map { |e| e }
 => [nil, 2, 3, 4] 

Similarly with select, except that's even more unhelpful with lazy:

> [nil, 2, 3, 4].select
 => #<Enumerator: [nil, 2, 3, 4]:select>
> [nil, 2, 3, 4].select { |e| e }
 => [2, 3, 4]
> [nil, 2, 3, 4].select.lazy.force   # doing it wrong looks functional!
 => [nil, 2, 3, 4] 
> [nil, 2, 3, 4].lazy.select { |e| e }.force
 => [2, 3, 4]
> [nil, 2, 3, 4].lazy.select.force   # same without .force
ArgumentError: tried to call lazy select without a block

Are these apparent identities (and ArgumentError!) useful, or just an opportunity for a better default in a future version of Ruby?

ndnenkov
  • 35,425
  • 9
  • 72
  • 104
android.weasel
  • 3,343
  • 1
  • 30
  • 41
  • `cycle` without a block returns an enumerator that infinitely loops (i.e. `next` can be applied indefinitely), so slightly different from the others. – Sagar Pandya Dec 09 '16 at 12:23

1 Answers1

4

First of all - a small remark. If you ever find yourself typing { |e| e }, you can instead use &:itself.


With that out of the way, enumerable methods without a block often times return an enumerator. You can use that to chain with enumerator methods. For example, consider:

[1, 2, 3].map.with_index  { |n, i| n + i } # => [1, 3, 5]
[1, 2, 3].each.with_index { |n, i| n + i } # => [1, 2, 3]

[1, 2, 3].select.with_index { |n, i| (n + 2 * i).even? } # => [2]
ndnenkov
  • 35,425
  • 9
  • 72
  • 104
  • Good, but since the OP asked specifically for select, you might show an example using select. – tokland Dec 09 '16 at 11:56
  • Mm, thanks for the quick response. `select.with_index{ |_, i| i > 2 }` does make sense, but it doesn't work with lazy, which seems like a bit of a hole. Thanks for the reference to `(&:itself)` - interesting and probably more efficient, but not as clear as `{ |e| e }` to my brain. :) – android.weasel Dec 09 '16 at 12:01
  • @android.weasel, now that you point that out - it is weird. You can still do it with `[1, 2, 3].lazy.with_index.select { :whatever }` (not the same, you will get the index in the result as well), but wtf, Ruby. xd – ndnenkov Dec 09 '16 at 12:05
  • `select.with_index{ |_, i| i > 2 }` would be just `drop(3)` – Eric Duminil Dec 09 '16 at 12:33
  • Until I chain or apply a block do `[1,2,3].each` and `[1,2,3].select` say return essentially the same enumerator? In contrast with something like `cycle` which behaves slightly differently when applying Enumerator methods. – Sagar Pandya Dec 09 '16 at 12:33
  • @sagarpandya82, they are not the same, it's just that you can't see the behaviour that is different until you do something meaningful with the enumerator. – ndnenkov Dec 09 '16 at 12:36