Ruby 2.7 new Enumerable methods

Ruby 2.7 adds a couple of new methods to Enumerable:

  • Enumerable#filter_map. For example: (1..10).filter_map { |i| i * 2 if i.even? } # => [4, 8, 12, 16, 20]. This is useful to avoid having to do .select { ... }.map { ... } which creates an intermediate array and it’s a very common idiom in functional languages (like Haskell and Elm).
  • Enumerable#tally. For example ['a', 'b', 'a', 'b', 'b'].tally # => {'a' => 2, 'b' => 3}. Counting things is pretty common, I think.

Should we add these to Crystal? They seem easy to implement and generally useful.

1 Like

For Enumerable#filter_map we already have Enumerable#compact_map which does just that!

4 Likes

tally is nice, i’ve even used such function few times.
But I have to say that (as non-native speaker) never heard this word so would never guess this name. Maybe it’s just me.

1 Like

I agree. The same happened to me too. I think Ruby chose that name because chances of it colliding with a user-defined method named like that is very unlikely.

1 Like

tally seems like a fine name to me, as an american (although I suspect it’s more widely used in England than the USA). As soon as I saw the method name, I knew what it was going to do.

On the other hand, it does seem to be true that I’ve never thought to use it in any program I’ve written, even though I have quite a few programs which have tallied things up like that!

For me at least, I think the name of filter_map is somewhat better than compact_map. And it’s one character shorter! :slight_smile:

Speaking of which, twould be nice to have the “lazy eval” type chaining so that no intermediate arrays are required [wait does that actually speed up things though? LOL]. And, following on after that, the java style “parallel stream” chains. Do those speed things up? Also don’t know…

Why we add tally, but not add filter_map? filter_map is it totally different with compact_map .

filter_map same as select {....}.map {...}
compact_map, is same as map {...}.compact

Technically you can use compact_map to do what filter_map does by just doing like next unless condition. The nil values will be filtered out so you end up with a filtered array from only a single iteration of the source collection.

Use Ruby or Crystal, prefer write clean code, filter_map is more clear than compact_map when do same thing.

in fact, both of them not same, compact_map only can do part of filter_map thing, we really should add filter_map instead of compact_map (or both), because the former is the superset of latter.

# Crystal
ic(1.14.0):005> [1, 2, 3, nil].compact_map { |i| i == nil }
 => [false, false, false, true]

# Ruby
[2] pry(main)> [1, 2, 3, nil].filter_map { |i| i == nil }
=> [true]
icr:5> [1, 2, 3, nil].compact_map { |i| i.nil? ? true : nil }
 => [true]

Not saying we shouldn’t add it, just that compact_map can work as a more efficient version of .select { }.map { } until if/when filter_map is a thing. This is really the first we’re hearing of a use case for it, so must not have been all that desired until now.

3 Likes

okay, we should not guide users to use compact_map instead of filter_map anyway, it’s really a BAD part.

There’s shorter and more idiomatic version:

[1, 2, 3, nil].compact_map { |i| true if i }