Add `Array#presence`

String#presence is a very useful method for when dealing with String? typed variables to ensure there is a non-blank, non-nil value. However, I sometimes notice myself wanting to something similar, but with an array. That is, perform some logic on an array that is non-nil, and not empty. Of which, having this method on Array, or even Enumerable could be quite handy.

I’m starting this off as a thread versus an issue as I’m not 100% convinced just yet. Like is this actually as commonly useful as I think it is? And/or is there actually a meaningful different between Array(String)? = nil and Array(String) = [] of String from a performance perspective?

The main benefit is when those arrays are mostly empty. Using nil avoids the GC allocation.

In the compiler is common, but I am not sure it’s really needed in most application’s code.

String#presence can be handy for handling HTML forms where "" functionally equivalent to nil. Most other usage I see of it would be better served by an actual nil.

One of the most confusing things for me when reading code is multiple-negative naming, like case_insensitive: false, so sometimes it can be nice to have a method where a method with a truthy return value indicates that it is a useful value. String#presence does this, but for arrays that’s already served by Array#any?.

Based on my experience with Rails, I feel like it would be used a lot, but I would draw a distinction between it being used and it being useful. I see if foo.present? in a lot of Rails codebases where if foo or if foo.any? would convey more information. Extrapolating from that (with the usual caveats about extrapolation), I think this would be misused more than it would be used correctly.

Usually when I need to accommodate an array not being provided (for example, some JSON APIs will simply not return empty array properties), I use something like this:

require "json"

struct Foo
  include JSON::Serializable

  getter my_array : Array(String) { [] of String }
  getter? my_array
end

foo = Foo.from_json(NamedTuple.new.to_json)

if array = foo.my_array?
  # ...
end
pp foo
# Foo(@my_array=nil) -- no allocation

foo.my_array.each do |item|
  # ...
end
pp foo
# Foo(@my_array=[]) -- allocated the array

This way, regardless of whether the remote API returns the array, I have the option to YOLO my way into it, which may allocate unnecessarily, or I can use the ? version of the method if I want to avoid unnecessary allocations when the array is not provided.

1 Like

Previous related discussions: