5 ms·
Perhaps I've been doing Ruby for too long, but it's still not that weird to me. The quantity "5" is very abstract without anything to have "5" of. That is why "
by WJW 1y ago
Perhaps I've been doing Ruby for too long, but it's still not that weird to me. The quantity "5" is very abstract without anything to have "5" of. That is why "5.days" and "5.times" exist, among others. Mathematically it makes just as much sense to start with the amount and add the unit later than it does to start with the unit and add the amount later (ie like `time_to_wait = SECONDS_IN_A_DAY * 5` as you might do in some other languages).
- torben-friis 1y agoMaybe it is clearer if I explain it in syntactic terms? In my mental model objects are nouns (described entities) and methods are verbs - actions over the noun. process.start() is the action of starting done by the the noun that is the process. It's not exactly a matter of naming, as some methods are not explicitly verbs, but there is almost always an implicit action there: word.to_string() clearly has the convert/transform implication, even if ommitted for brevity. I see no path where 5 is a noun and times the verb, nor any verb I can put there that makes it make sense. If you try to stick a verb (iterate?) it becomes clear that 5 is not the noun, the thing performing the iteration, but a complement - X iterates (5 times). Perhaps the block itself having a times object with 5 as an input would make it more standard to me (?). But I do understand that if something is extremely practical a purist/conceptual argument doesn't go very far.
- oezi 1y agoI have been doing Ruby for so long that it feels very natural to apply a method in this way on the instance. false.not applies the not method on the false instance in the same way that car.start in every OO language calls the start method on car as the receiver. So filter(list) feels just wrong when you are clearly filtering the list itself.
- nasmorn 1y agoAlthough I prefer Elixir currently I agree that ruby at least goes all the way in on OO and not having to remember which feature is implemented as a language syntax and what is just a method invocation is a strength not a weakness. It is different in other languages for historical performance reasons really.
- torben-friis 1y agolist.filter is ok! Filtering is an action that applies to a list false.not is borderline but if read as false.negate it makes sense (negating is an action that applies to a Boolean value). That wording screws the chaining though. 5.times is where the pattern breaks: times is not an action that applies to a number (nor an action at all). It’s the block the one that should repeat/iterate - but Ruby breaks the rule there and blocks are not an object (!). If they were you could block.repeat(5) which IMO is cleaner.
- chao- 1y agoThere is a bit of personal preference in what "applies to a number", but I see what you mean. As a slight correction, a block is indeed an object! They are received by methods as an instance of the Proc class: def inspects_block(&block) puts block puts block.class end inspects_block { "foo" } # => #<Proc:0x0000000000000000> # => Proc You can even add a 'repeat' method to these in the way that you specified, although you will need to add '->' to declare the block (as a lambda, which is also just an instance of Proc) before you call #repeat on it: class Proc def repeat(n) n.times { self.call } end end ->{ puts("foo") }.repeat(3) # => foo # => foo # => foo
- kaiuhl 1y agoBlocks are actually instances of the Proc class. There are helper methods to handle blocks passed to methods in a lightweight manner but you can also accept the block as a method argument, e.g., class Integer def times(&blk) i = 0 while i < self blk.call(i) i += 1 end end end
- dragonwriter 1y ago> Blocks are actually instances of the Proc class. No, they aren't. Blocks can be reified into instances of the Proc class, but they are not objects and reifying them into objects has overhead. Using a & argument asks for a block passed to the method to be reified into a proc accessible in the body of the method, which is useful if you are going to do something with it that you can't do with a bare block.
- bigtunacan 1y agoIt’s not just about practicality. Ruby is using message passing, not method calling. This is fundamentally different and a bit foreign to the larger community. Then ruby layers syntactic sugar on top that hides this. Behind the scenes everything is a message passed using __send__ and you can do this directly as well, but you generally don’t. So when you write 5.times { puts "Hello" } It’s sort of expected by the average programmer that you are telling 5 to call the times method and expect it to exist and do what it’s told. In reality you have indirectly sent a message that looks like 5.__send__(:times) { puts "Hello" } What we are really doing is sending a message to 5 (the receiver) and giving it the opportunity to decide how to respond. This is where method_missing comes in to allow responding in a custom fashion regardless if a method was explicitly defined. So you’re not telling 5 to call the method times, rather you are asking, “Hey 5, do you know how to handle the message times?” These are fundamentally different things. This is actually super important and honestly hard to really grok _especially_ in ruby because of the syntactic sugar. I came from a C/C++ background originally, then Java and then moved to Ruby. After a few years I thought I understood this difference, but honestly it wasn’t until I spent a couple years using Objective-C where message passing is happening much more explicitly that I was able to truly understand the difference in a way that it became intuitive.
- anamexis 1y agoI’m a rubyist, but how is message passing fundamentally different from method calling? I get that method_missing adds a twist, but your comment doesn’t explain what the fundamental difference is. Especially in the context of Fixnum#times. How does message passing vs method calling matter there? #times is just a method on Fixnum.
- isr 1y agoBecause it leaves it up to the object being called what to actually do with the message. The object you're talking to might be forwarding its messages to another object in another ruby instance on another machine (if the current machine is getting fully loaded, etc), and the caller would be none the wiser. And crucially, the caller wouldn't have to be modified to enable this. The logic for this would be entirely within the object being called. So the difference isn't just with method_missing. With "method calling" as you put it, the program blows up if that object doesn't have that method, WHEN YOU CALL IT. Basically, this smalltalk oo paradigm is about shifting where you put more of your logic. At & around the "call" site, or within the object whom you're calling & entrusting to do something useful with it. All hearkening back to Alan Kay's original ideas about biology influencing how we organise code, and having a program be 1000's of "little black boxes" where work gets done by all these boxes talking to each other. Which is why smalltalk (& ruby implements the Smalltalk object model to its entirety) actually has an awful lot in common with Erlang & other BEAM runtimes, even though those are functional languages. Because once you get beyond the techy buzzwords, the main ethos behind them is actually quite similar.
- dragonwriter 1y agoIn a pervasively object oriented system/language, anything that can be represented in the system is an object (=noun =potential agent of action). Ruby is almost a pervasive OO system (falling short because blocks are not objects, though they can be reified into objects with some efficiency overhead.) As such, unreified blocks are the only thing in Ruby that cannot be the agent of action. For an iteration of a block a fixed number of times, the potential agents are the block and the number of times. But blocks aren't objects, so they can't be the agent, leaving only the number. Hence, Integer#times