8 ms·
Weird Ruby: Nil Conversions
- brink 3y agoI know companies that write banking software in this language. I wouldn't. But I guess if you write enough unit tests on the software and remember to avoid gotchas like this you'll probably be fine, and I think that's how they feel. To each their own, I guess.
- ksec 3y agoSometimes I do sort of wish there is a subset of Ruby that is more Pythonish. At least the Zen of it.
- sethrin 3y agoHow do you feel about Crystal-lang?
- ksec 3y agoI specifically avoid mentioning Crystal because there are a group of people on HN who doesn't like Crystal being mentioned in a Ruby Thread. But yes Crystal would be close if not "it".
- drzaiusx11 3y agoThis is basically crystal is: a Ruby-like with inline type annotations like python3 and some of the "surprises" removed like Ruby's nil handling
- masklinn 3y agoWhat part / domain of "banking software" probably helps as well e.g. for a web frontend or other gateway to more resilient backends it might make sense enough.
- Calavar 3y agoI really don't consider this to be a gotcha. If you explicitly cast to type X, it isn't surprising to get back an object of type X, right? These aren't implicit type conversions a la the Javascript + operator
- bobbyi 3y agoIt is if the object can't be meaningfully cast to the type. I'd expect to get an exception. That's why they exist. For example, consider explicitly casting the string "abc" to an int. Python throws a ValueError. Ruby silently ignores the problem and gives 0. I consider the Ruby behavior to be a gotcha.
- vidarh 3y agoIf you want to "explicitly cast" to an Integer only when the string strictly represents an int in Ruby you shouldn't be calling to_i. The Ruby behaviour is only a gotcha of you don't know the proper way of doing this in Ruby. If you want an Integer or an exception you would call Integer(). If you want an Integer or nil, you would call Integer.try_convert(). If you call to_i, you're explicitly asking for a best-effort conversion, and shouldn't be surprised that's what you get.
- zrail 3y agoHaving worked in a handful of fintechs that use Ruby extensively, this generally isn't a problem in practice. In my experience there's a Money type that gets passed around rather than raw numbers, and that Money type will enforce a bunch of constraints, among them non-nilness.
- decasia 3y agoI write Ruby professionally and while I agree that this can lead to some very sloppy code, I don't find that this causes a lot of problems in practice. A place where we would use nil conversions is especially in logging and error handling — you can cast anything, including nil, to a string, and that is useful in debugging. Probably more often than nil conversions, we use the safe navigation operator &. as a way of working with "might be nil" values without having to add too much verboseness. I do miss the way languages like Swift think about Optional values, where you have to be much more explicit about whether or not you are dealing with `nil`. It's easier to reason about and leads to clearer codepaths. And in general, I miss stronger type systems in Ruby. But in a professional environment with relatively high developer expertise in the language and relatively clear style guides, we're still very productive in Ruby and don't suffer hugely from the typing issues. I guess eventually you learn to work around the warts in whatever language you use.
- masklinn 3y ago> A place where we would use nil conversions is especially in logging and error handling — you can cast anything, including nil, to a string, and that is useful in debugging. Lots of languages can format anything to a string though. Even Java will let you write "foo " + null And logging / error handling / debugging seems like the context where you would least want nil to become an empty string (and thus be confusable with an actual empty string, or not appear in the output).
- epidemian 3y agoI agree with the grandparent general point, but i would say that it is string formatting more precisely where these nil conversions are useful. For example, you can write something like: bananas = 4 puts "you have #{bananas} banana#{'s' if bananas != 1}" And this works because: `if` is an expression, `if` evaluates to `nil` if the condition is not met and there's no `else`, and `nil.to_s` is an empty string. > And logging / error handling / debugging seems like the context where you would least want nil to become an empty string Yep, 100% agree. And i think Ruby's designers agree too, since the language has the shortest method possible, `p`, for print-debugging. You can do `p something` and that'll print a useful string for debugging (`""` for an empty string, `nil` for nil, `[]` for an empty array, etc).
- rymiel 3y agoCrystal doesn't let you do these weird things
- transfire 3y agoThere is nothing “dangerous” about this if you know the behavior. In Ruby nil is a representation of emptiness, so when using the to_ conversion methods you will get an empty instance of a type. If you don’t want this behavior you use a constructor instead. FYI For a long time there was no nil.to_h and there was some consternation about adding it. But finally it was embraced and this emptiness notion solidified into the language for good.
- eyelidlessness 3y ago> There is nothing “dangerous” about this if you know the behavior. There’s nothing dangerous about any functionality in any language if you know the behavior. Not that I’m saying this is a particularly dangerous functionality of Ruby (coming mostly from other dynamic/scripting languages, it seems refreshingly explicit if nothing else), but I don’t think expertise in usage of a footgun is what should determine whether it’s a footgun.
- sebastianconcpt 3y agoMapping nil to 0 is a path to create an incorrect universe, tho.
- crazydoggers 3y agoBut in set theory, zero is the empty set! So Ruby creates the correct universe =D nil = nil nil.to_i = nil.to_h 0 = {}
- sebastianconcpt 3y agoYou made me think :D Processing...
- kazinator 3y agoThis is because in Common Lisp, nil is an instance of the null class, the one and only instance. You can specialize method arguments to the null class, so that they capture nil arguments, which is very useful and good. (Mind you, I can't think of anything built into the language which exploits this, let alone for converting nil to 0.0.) Some things in Ruby are patterned directly after Lisp. You notice it here and there, like the #<...> notation for unreadable objects, or calling small integers fixnum, ... In the world of static reference OOP like Java and C++, there is the Null Object Pattern: https://en.wikipedia.org/wiki/Null_object_pattern https://en.wikipedia.org/wiki/Null_object_pattern Because nil is the empty list, one use for specializing on the null class is for handling the empty case of a list. (defmethod do-some-list-thing ((list null)) ;; empty list case ...) (defmethod do-some-list-thing ((list cons)) ;; non-empty case ...)
- entropie 3y agoI code ruby (not exclusively) for like 20 years now, that must be the reason I dont find this odd at all. Which example from the list is weird? I would not write var_that_might_be_nil.to_h I find this more understandable var_that_might_be_nil || {} But I find both acceptable and fine.
- sethrin 3y agoMost often I do var_that_might_be_nil.presence || default reads better imo
- vidarh 3y ago"presence" isn't Ruby, but Rails/ActiveSupport and your example acts very differently in the case of empty strings, empty arrays, hashes etc..
- vidarh 3y agoI agree if it is known that the value can only be Hash or nil. But to_h often makes sense when you need a Hash, and there are other possible values too.
- mistercow 3y agoThese are pretty closely aligned with “zero values” in Go. I think “zero value” is clearer terminology than “carrying the concept of nullity”. It’s a lot more natural to ask “what’s the zero value for this type” than “how is the concept of nullity carried to this class?”
- teeray 3y agoWhen I first wrote Ruby and was new to programming, and I got the typical “undefined method X for NilClass”, I did the logical thing: I defined X on NilClass. I still have nightmares from that self-inflicted madness.
- zoky 3y agoYou fool! You should have defined `method_missing’ on NilClass for extra-spicy good times!
- block_dagger 3y agoAs others have mentioned, the safe navigation operator (&.) obviates the need for converting nil to "default" cast values. Would be nice if Integer(nil) worked like Array(nil) though.
- stinky613 3y ago> Would be nice if Integer(nil) worked like Array(nil) though. How would that work, though? A value of 0 is conceptually different from not having a value
- vidarh 3y agoI go back and forth on this. We "need" what Array() currently does. Arguably we need, and typically use it, far more than Integer(). I'd be inclined to argue both represent the most commonly desired (in Ruby anyway) treatment of input. Silently converting an integer is very often very bad, but being able to pass either single values or an array is often very good, and in the same cases guarding against nil is also very often desirable. At the same time it might be nice if they had names that were differentiated to indicate the behavioural difference, but I don't know what a better, similarly short option would be. We also do have "Integer.try_convert" and "Array.try_convert" which both consistently will refuse to convert "nil", though returning nil on failure instead of raising an exception, but I very rarely see it used.
- mberning 3y agoI worked exclusively in Ruby for many years and this was never a problem I experienced. Or if I did it was such a non issue it was fixed and moved on. I can agree that the behavior of casting to a number type should probably be undefined. But a null becoming an empty set seems fine to me.
- vidarh 3y agoInteger(nil) will raise a TypeError, as will Integer("4x"), but not Integer("4"), so when you want that behaviour, just use Integer(), or Integer.try_conver() if you want it to return nil instead of raising an exception.