3 ms·
Why are there so many kinds of things that can be passed to a Ruby function?
by throwanem 3y ago
Why are there so many kinds of things that can be passed to a Ruby function?
- yebyen 3y agoYou can pass positional arguments because that's how arguments have typically been passed in the history of computing. You can pass keyword arguments because they are much less ambiguous to use when a method signature is changing; you never have any confusion about which method argument is missing. You can pass positional and keyword args together in a way which is rigidly formalized since Ruby 3.0 because, at one point in history, you could pass a hash, you could pass positional arguments, and you could pass keyword arguments, and nobody was ever able to tell reliably what you were doing: https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-keyword-arguments-in-ruby-3-0/ https://www.ruby-lang.org/en/news/2019/12/12/separation-of-p... You can pass a block because in Ruby, a block is an object like anything else. And you can pass an anonymous block because in Ruby, the "yield" keyword is an idiom that's commonly used with anonymous blocks. The curly braces and the "do" keyword that are both common idioms in Ruby are nearly identical forms of anonymous block. The throwaway answer to your question is that you can pass anything that is an object, and in Ruby since anything is an object, you can just pass anything! I haven't read the whole article, but I get the sense that I shouldn't read much into the clickbait headline, since it looks like this is a really thoughtful show or treatment of all the neat handy things that Ruby lets you do with various types of arguments, and not a hit piece! whew relieved
- throwanem 3y agoI would call it a thoughtful treatment of how late versions of Ruby solve a problem that well-designed languages never have in the first place.
- endorphine 3y agoI intuitively expected this to get downvoted but I'd like to hear opinions of the people who downvoted this, wrt how much of this flexibility is a design flaw (or accidental complexity?) and how much it is expected given the history and evolution of the language.
- lamontcg 3y agoThey didn't comment on what they think good design was so its just pretty uselessly taking a swipe at Ruby. They may be correct that languages made more recently are better designed from the start because they can work around issues that ruby hit because people largely didn't know any better (some contemporaries may have claimed to know better, but there's always people after the horse race who really did bet on the right horse). They may be starting a religious war, believing that something that ruby supports is just so clearly wrong that ruby shouldn't exist. We can't really say, but it doesn't offer any useful comment. And its really easy to say that a 28 year old language shouldn't have been developed exactly the same way, given what we know now.
- throwanem 3y agoJavascript and Ruby are of an age, both first released in 1995. Javascript has always supported all of the use cases described here for Ruby, until very recently with exactly one syntactic convention for argument passing. If you want keyword arguments, pass an object; if you want to pass a function, pass a function. That's simpler to understand because there is less to understand, with these capabilities composed from extant syntax rather than having been invented as one-offs that require being learned and understood as such, each hedged around with its own special cases and failures to generalize - if that were not the case, the article here under discussion and all others like it would never have needed writing in the first place. I realize describing Javascript as "well designed" will be controversial, and certainly it has not been without its share of flaws: loose equality and IEEE 754 float behavior come most quickly to mind, along with the lack of a general conditional expression. I am comfortable with describing Javascript as better designed than Ruby, though. Certainly it is far more ergonomic. Good engineering above all else is, as much as possible, simple: it can't help being about as complex as the domain it addresses, but any complication beyond that represents an error of design. Programming a general-purpose computer is an arbitrarily complex domain, which gives all the more point to the need for simplicity in design: the user of the tool has enough to think about already, and should insofar as possible not also be forced to manage unnecessary complexity in tooling. Under such a metric, that Ruby needs three kinds of calling conventions to accomplish what Javascript does with one is already enough to demonstrate that Ruby's design is lacking.
- codesnik 3y agoin my opinion, Ruby (and also aforementioned perl back in it's era, but this would be pointless to defend) is one of the languages where syntax and semantics looks like they took order of magnitude more thought than most of other languages.
- Pxtl 3y agoThere's only 3. Named args, unnamed (positional) args, and a special block. Because Ruby was basically "let's make Python but without Guido's hatred of metaprogramming", so it copied Python's *args and **kwargs and then added the block so you could make functions that look like control blocks. To me the problem is that they're handled as 3 vars instead of 1 object that has 3 public properties.*
- 38 3y ago[flagged]
- corytheboyd 3y agoIt’s really just positional arguments, keyword arguments, and block, in that order, all optional.
- masklinn 3y agoAssuming you're not talking about values, there's really two things which can be passed to a ruby function: normal arguments and blocks. And arguments may be passed either positionally or by name. Blocks are the weird part, they have special support because they're a bunch of syntactic sugar / magic, which doesn't even have to be part of the function's prototype e.g def foo yield if block_given? end "yield" will invoke the implicit block, "block_given?" is a metamagic kernel method which checks if the current context was passed a block. I haven't kept up to date with Ruby so I don't know if that style is still en vogue. The equivalent "explicit" version is def foo &block block.call if block end in which the block — if passed — is reified to a Proc, which you can manipulate normally. If no block is used, then `block` will just be `nil` (as the essay notes, blocks are always optional).
- mdavidn 3y agoThese two forms are equivalent in modern Ruby, but they weren't always. The explicit &block required a heap allocation to close over the block's local scope. This is a "reified" Proc object. The implicit block could remain on the stack, since a block never outlives the stack frame that defined it. Fun fact: Both yield and block.call may never return. The block is free to return from its enclosing method, which effectively pops multiple frames, including foo and possibly more, off the stack.
- shadowbanned4 3y ago> The block is free to return from its enclosing method, which effectively pops multiple frames, including foo and possibly more, off the stack. As an aside, this is one of the key differences between a lambda and a proc. Last I checked, you can pass a lambda as a block, but it is implicitly converted to a proc.
- throwanem 3y agoI'm glad you mentioned that! The existence of three distinct abstractions over the concept of function values is another good example of Ruby's flawed design.