I noticed there are these two functions in the std lib
Nil#presence
String#presence
wondering if we could not expand this to all object types. Something like this
class Object
# returns its receiver if it's not nil? or empty?
def presence
_self = self
if _self.responds_to?(:empty?)
_self if !_self.empty?
else
_self
end
end
end
# this is the existing implementation on String and Nil
class String
def presence
self if !blank?
end
end
class Nil
def presence
self
end
end
I think we could introduce blank? for all objects, then present? would be the opposite and presence would rely on blank?.
In the beginning I was reluctant to add blank? but eventually we did it for String. Maybe it wouldn’t hurt to add it to other types, I don’t know. presence if useful in the example you provide, I think it’s the reason why it’s there in String and maybe Nil.
We discussed this when introducing String#presence/Nil#presence, see https://github.com/crystal-lang/crystal/pull/1299 and the discussions referencing it, especially the ones within the PR that added String#presence.
Asking because I see this exact concept misused everywhere in Rails apps, where people treat empty arrays, hashes, and strings as equivalent to nil throughout the entire app instead of the one layer that interfaces with HTML form input (where there is no distinction between empty and omitted).
NoSQL databases storing JSON configurations mainly, parsing binary network protocols, also web front-ends (these are pretty common input interfaces these days).
i.e. https://opcfoundation.org/about/opc-technologies/opc-ua/ as an example network protocol
Right now it’s writing an application that interacts with Google Calendar API - so lots of JSON
It feels like something that comes up once a day and after looking at the link @jhass shared, the number of pull requests made, it’s probably not just me.
The problem is not one of usecases but one of semantic. What kind of values are considered “present” is quite domain specific and doesn’t have to be the same across domains. In that regard String#presence is already a big stretch for stdlib. I feel it okay pretty much only because it’s not defined for other types but Nil. We can claim a definition within the domain of a “string” rather than the domain of what the string represents in the user code. But this distinction falls apart when we start to add things like Array#presence. What’s the common domain of a string and an array, what’s the common reasoning between their presence definitions?
My link served to link the existing discussions, please don’t just ignore them and start from scratch on the topic.
it is not empty or blank (blank being a superset of empty)
If you think of an array in C, it’s just a pointer to memory. If there is nothing in the array, size zero, there is nothing present in memory for that array. In C land you literally want this to be null pointer.
A hash has similar semantics to an array when empty - there is nothing present in the hash.
An integer is present no matter its value, it exists and is present as an integer.
So from this I would argue if a thing is not empty then it has something to present, hence presence.
I would also say that things don’t have to be semantically perfect to be useful. A save icon is a floppy disk, no semantic value to a large group of people, but still a useful button
The floppy icon actually made perfect sense when it was introduced, people just learned to associate a particular concept to it as the exisiting association started to become weaker. Introducing a concept with weak associations from the start is a whole different story.
You just delegated the ambiguity to “blank” in your definition. When is an object blank? Why is an all whitespace string blank but an array consisting of just nils not? Or if we follow your C analogy, why is an array of 0x20s not blank?
The answers to these questions are specific to the application domain and not the data structure domain. This is why in the Ruby world these methods continue to live in ActiveSupport, a collection of helpers for a web framework.
Strings are typically designed for human consumption. So a string with nothing visible to a human eye is blank to us if it has 1000 characters or none.
However Arrays are for computer consumption. So an array of nils, possibly represented by an array of 0’s in memory has meaning to a computer.
So semantically it makes perfect sense that a string of nothing has no presence but an array of 0’s is present. Ruby has the unfortunate situation of using strings for binary data storage whereas crystal has slices - so we can take this semantic differentiation in our stride.
Finally I point to the literal motto of crystal lang
I don’t see that being brought up as an objection in the Ruby issue tracker entries I linked about this topic, so maybe that’s not their reason for being hesitant about it? ;)
I agree with @jhass. Specially because coding a shard with blank?, present? and present is very easy, maybe just 50 lines of code. Then you can use it in your project, and if that semantic doesn’t fit your domain then you can define them in a different way.
I agree with the idea of making this a shard or even just adding it to your own app. I’ve found that counting an empty array/hash the same as nil is not a good general practice. IME it has always led to sloppy code, where treating the two as equivalent propagates outside the one layer of the app where that equivalence makes sense.
Also agree with @jhass and @jgaskins .
In particular, better avoiding the use of not_nil!, and even .as if possible. @stakach In my opinion it is cleaner to do
unless my_var && !my_var.empty?
my_var = ["default", "array"]
end
# Instead of
my_var = ["default", "array"] if my_var.nil? || my_var.try(&.empty?)
my_var = my_var.not_nil!
I would argue it is less readable. More lines of code, easier to make a mistake and for everyone who hasn’t seen your example has all the opportunities to introduce a less than ideal implementation.
This may be a futile discussion but I still haven’t seen a very compelling argument not to introduce it.
The linked Ruby issues are mostly discussing Object#blank? whereas Object#presence as I defined it above seems simple enough to get one’s head around
Further, if the more complicated example above is super clear why don’t we use this pattern for strings?
It is more confusing to have a special case for String and nothing else - especially when a function of String is leaking into the implementation of Nil - I makes way more sense to have String and Nil special cases of Object