8 ms·
Ruby gaining a safe navigation operator: '.?'
- 9point6 11y agoI've been hoping this would show up in JavaScript after playing with CoffeeScript a few years ago. Being able to do: foo?.bar?('baz') was quite nice.
- V-2 11y agoRuby can't use "?." operator. Can we use ".?" syntax Why couldn't Ruby use "?."?
- griffinheart 11y agoIts in the issue comments def foo? Object.new end foo?.class since "foo?" is a valid method it would conflict with "?." Edit: with ".?" looks like "foo?.?class" Edit 2: side note, this would never pass a code review, the expectation of "foo?" is that it returns true/false.
- tom-lord 11y ago> this would never pass a code review, the expectation of "foo?" is that it returns true/false. 95% of the time, yes. However, the "official" rule is simply that such methods must return a value that can be EVALUATED as true/false. There are plenty of methods in the ruby core/standard library which end in a "?" but do not return true/false, such as: * File#size (http://ruby-doc.org/core-2.2.3/File.html#method-c-size-3F http://ruby-doc.org/core-2.2.3/File.html#method-c-size-3F) returns Integer or nil. * Integer#nonzero? (http://ruby-doc.org/core-2.2.0/Numeric.html#method-i-nonzero-3F http://ruby-doc.org/core-2.2.0/Numeric.html#method-i-nonzero...) returns self or nil. This misunderstanding of what "?" methods are allowed to return is an ongoing problem in the community, e.g. https://github.com/rails/rails/issues/20110 https://github.com/rails/rails/issues/20110
- deleted 11y ago[deleted]
- phindmarsh 11y agoI'm no rubyist, but I think it is because ? is often used at the end of method names This means that something like foo.thing?.bar is potentially ambiguous.
- pluma 11y agoFor whatever reason, Ruby allows method names ending with ? or !. AFAIK the general practice is to use ! for methods that perform a mutation in-place (e.g. list.sort! would sort a list in place, list.sort would be expected to return a new list) and to use ? for methods that return a boolean (e.g. list.empty? would indicate whether the list is empty).
- cremno 11y agoRuby inherited this from Scheme (and I prefer empty? over isempty or emptyp or variations thereof). They return truthy or falsy values and not true or false (the is* macros/functions in C only guarantee zero and nonzero as return value either). >AFAIK the general practice is to use ! for methods that perform a mutation in-place The ! suffix is rather loosely defined as it just means the user should be cautious/the method might have "dangerous"/surprising behavior. Your description is usually correct but only when a non-suffixed version exists and not even then the behavior has to match your description. There are also a lot unsuffixed mutating methods. See also Kernel#exit vs. Kernel#exit! (immediate exist/exit handlers aren't called) or Tempfile#close vs. Tempfile#close! (close and unlink).
- dragonwriter 11y ago> AFAIK the general practice is to use ! for methods that perform a mutation in-place That's not quite the usual statement of the rule: ! is for methods where there exists a "dangerous" version and a "less dangerous" version, to denote the more dangerous version. A common example of that is methods where one version returns a copy of the original object with some modification applied and the other version modifies the original object in place, but IIRCT that's not the only distinction it is used for in the standard library and, more importantly, in-place mutations that aren't paired with a "return-a-modified-copy" method don't get the !.
- lsaferite 11y agoPlease define IIRCT as I cannot find what it's meant to mean.
- VeejayRampay 11y agoShould be noted that Ruby on Rails has had try [1] for years, which is something that I've noticed has a tendency to be the classic band-aid for loose error handling. [1] http://www.rubydoc.info/docs/rails/Object:try http://www.rubydoc.info/docs/rails/Object:try
- moonshinefe 11y agoDoesn't this just encourage lazy and vague programming? It sounds like it cuts down on a lot of checks in certain situations, but I'm struggling to think of a coding pattern that's actually well designed that requires so many checks at the last minute without knowing what type and data you're dealing with.
- moonshinefe 11y agoTo clarify, in the article linked they give this example: u.?profile.?thumbnails.?large Using that operator a single time or whatnot seems handy, but chaining it like that just seems like poor practice to me.
- deckiedan 11y agoIt's in deep nesting like that that such an operator really comes into its own. If you only use it for single cases, it really isn't worth it. In the article the 'problem' code is: if u && u.profile && u.profile.thumbnails && u.profiles.thumbnails.large do_something_with(u.profiles.thumbnails.large) fi which repeats u 5 times, and the entire u.profiles.thumbnails.large twice! which would be replaced by only asking once. even worse (although somewhat clearer, perhaps) is what I've occasionally seen: if u if u.profile if u.profile.thumbnails if u.profile.thumbnails.large do_something_with(u.profile.thumbnails.large) end end end end And even the super verbose version there still can raise exceptions when you get slightly mad data - say you decode some JSON which has u.profile.thumbnails as "null" (a string) rather than actually a null. asking if u.profile.thumbnails will be true - but when you request u.profile.thumbnails.large, you blow up. You can achieve similar effects using only exceptions, but there's a good argument for using exceptions only for exceptional circumstances. begin do_something_with(u.profile.thumbnails.large) rescue NoMethodError #or similar # oh no! end which (alas) also will catch all noMethodErrors in do_something_with. So you then end up with begin tmp = u.profile.thumbnails.large rescue tmp = nil end if tmp do_something(tmp) end which is super ugly. I may have got details wrong - not being a rubyist, but I think this is the general gist of where a nestable operator makes much much cleaner code, which catches more errors.
- nikon 11y agoSimilar to the new null-conditional operator in C#? https://msdn.microsoft.com/en-us/library/dn986595.aspx https://msdn.microsoft.com/en-us/library/dn986595.aspx
- richmarr 11y agoGroovy's predates this one, but yeah, looks similar.
- vorg 11y agoI remember Groovy didn't allow ? on indexes like in the third C# example: int? count = customers?[0]?.Orders?.Count(); I remember asking for it but it never came. I don't think Ruby's proposed .? syntax would work with indexes either unless they use [? ]. Neither Java nor Groovy has the int? declaration shown which enforces the optionality. The C# version looks more versatile. Back when Groovy was more popular, its backers also discussed a cascading ? on their mailing list but it never happened. If C# had cascading ? then the example above would be int? count = customers?[0].Orders.Count(); because the first ? would ripple to the subsequent member accesses and index lookups.
- richmarr 11y agoYep. I remember waving my arms and crying for ES6 to have '?.' back when the spec was open, but there was a lexical issue. It's frustrating to have such useful features blocked by such seemingly small problems as ambiguous patterns of characters.
- moonshinefe 11y agoIndeed it seems similar. I can't recall if the C# one allows chaining however.
- biot 11y agoThe third C# example shows: int? count = customers?[0]?.Orders?.Count(); So yes, it can definitely be chained as the following paragraph in the linked page explains.
- maniacalrobot 11y agoSeems like a perfectly valid opportunity to use the Interrobang character? foo‽bar("baz")
- forgotpwtomain 11y agoI don't understand why this needs a special syntax -- to me ruby already has a little too much syntactic variation. Why not just extend fetch or try e.g. -- .try(:profile, :thumbnails, :large)
- maniacalrobot 11y ago"try" is an active support extension on "nil", so if you're using rails (or active support), then you already have this functionality. The point being made here is that patterns developed outside the core Ruby development process are getting adopted upstream by the language for all Ruby applications to benefit from. Also, there your example or using try falls short if the methods require additional arguments to be passed in. "try" already handles this by requiring you to "try" each method. Of cause there is a very good argument to be had that this pattern also breaks the law of demeter by allowing you to call methods on objects that you don't own a stake in.
- hiharryhere 11y agoFYI .try takes a block. Passing a symbol is just shorthand, similar to how map(&:method) is shorthand for .map {|x| x.method } So technically you can pass arguments to a try method by doing object.try {|o| o.method(args) } Not trying to be pedantic, hopefully being helpful.
- maniacalrobot 11y agoEveryday is a learning day!
- spoiler 11y agoI agree with this. I don't see why new syntax (which looks kinda confusing, IMO) should be added to solve a problem which can be solved with a method. A naive-2-minute implementation: module Chainable def chain(*kl) k = kl.shift if self.respond_to?(k) v = self.public_send(k) else v = nil end return v if kl.empty? unless v.respond_to? :chain raise ArgumentError, "can't chain on `#{k.inspect} => #{v.inspect}:#{v.class}`" end return v.chain(*kl) end end and then this: class Object include Chainable end
- gotchange 11y agoI am not familiar with Ruby except for the casual exposure to it through Sass but couldn't he just go for only if u.profiles.thumbnails.large instead of these successive checks?
- forgotpwtomain 11y agoNo, because you cannot access thumbnails on profiles = nil.
- gotchange 11y agoWhy can't I access it? I'm genuinely curious about this.
- spoiler 11y agoBecause nil in Ruby is essentially a NULL, or it holds no value. Analogy: It's like asking for a picture of a person that doesn't exist.
- pdkl95 11y agoWhile it's probably a good idea to treat nil as a null pointer that doesn't do anything, it's still an an instance of NilClass and can be used as an object. >> class NilClass • def foo! • puts "bar?" • end • end => :foo! >> nil.foo! bar? Rails uses[1] this with the very-useful #blank? method. [1] https://github.com/rails/docrails/blob/master/activesupport/lib/active_support/core_ext/object/blank.rb#L46 https://github.com/rails/docrails/blob/master/activesupport/...
- spoiler 11y agoIt's arguable whether NilClass#blank? is very-useful (the same can be said for #to_a, #to_c, #to_f, #to_h, #to_i, #to_r, and #to_s). I'd call it marginally convenient, because you have to type less, but it doesn't increase usability, and the readability is pretty much the same (or worse, because someone would have to know NilClass responds to blank?, if they don't they might've expected an empty array, or an empty string). Also, why does FalseClass respond to blank? with `true`, but not TrueClass (I didn't know it did this for those two until now)? How is false less blank than true?
- Udo 11y agoI think a good case could be made that, if you're designing a new dynamically typed language from scratch that doesn't have to support older code, safe array/object navigation should be the default. My argument for this is you should design for the most common use case first, and most commonly people will want '.?' behavior. You can still check explicitly for null in the other cases, or even introduce a more cumbersome non-safe navigation operator. The resulting code will overall need fewer checks, be more readable, you'll have fewer crashes and exceptions, and it will generally do the right thing. I occasionally write toy languages and this is one behavior I think I got right recently. Along the same lines, but as a more extreme measure, I would argue that invoking a method call on a null object should return null instead of throwing an exception.
- skrebbel 11y agoI wildly disagree, because I get waking nightmares from the amount of implicit "wtf why doesn't it even enter my method" behavior that would imply, but I upvoted you because I think it's a very interesting idea and I wonder what others think.
- Udo 11y agoI agree that the invokation idea is relatively wild, that's why I made it into a separate point. There are somewhat successful languages out there where sending a message to a null object doesn't do anything. If this is a class of bug that happens to you frequently, it's probably not for you. But once you get into the mindset that each method call is prepended by a virtual conditional, my hunch is you would know pretty quickly why your method isn't being called.
- seivan 11y agoGood, I guess it's a way to legitimise .try() I like it.
- alexprengere 11y agoInteresting timing, python-ideas has also been discussing this, leading to a new PEP 505 - None-aware operators: https://www.python.org/dev/peps/pep-0505/ https://www.python.org/dev/peps/pep-0505/
- colinyoung 11y agoMaybe we could use something more readable? What about: if posts.comments??.likes?? or if posts..comments..likes instead of if posts.?comments.?likes That would mirror the usage of ?? as the nil-coalescing operator in Swift.
- Slump 11y agoAffectionately called the "Elvis operator" by my development team.
- vorg 11y agoElvis is actually the name in Groovy for the ?: syntax (C# uses ??), not for the ?. syntax. The name "elvis" was promoted by the then-project manager of Groovy in an attempt to change the origin meaning of "G-Strings", the name for interpolated strings in Groovy. It originally alluded to the item of clothing, but in an attempt to sanitize Groovy's image for marketing, he wanted it to have meant the string on Elvis's guitar, so he created an accompanying fun-name for the null coalescing operator.
- Slump 11y agoThat's fair. I come from a C# background and Mads Torgersen himself refers to the C#'s ?. syntax as the Elvis operator see (https://channel9.msdn.com/Events/Build/2015/3-711 https://channel9.msdn.com/Events/Build/2015/3-711) 30:50. It's all in good fun.
- deleted 11y ago[deleted]
- Camillo 11y agoThat thread has some incredibly bad syntax ideas towards the bottom (#22, #23). A binary operator with a space in the middle?