4 ms·
> The value 5 having a method, and that method being an iterator for its value, is kinda weird in any design sense and doesn’t seem to fix any architectural ord
by WJW 1y ago
> The value 5 having a method, and that method being an iterator for its value, is kinda weird in any design sense and doesn’t seem to fix any architectural order you might expect, it’s just there because the “hack” results in pretty text when used.
I don't think this is particularly weird, in Ruby at least. The language follows object orientation to its natural conclusion, which is that everything is an object, always. There is no such thing as "just data" in Ruby, because everything is an object. Even things that would just be an `int` in most other languages are actually objects, and so they have methods. The `times` method exists on the Integer classes because doing something exactly an integer number of times happens a lot in practice.
- torben-friis 1y agoI don’t have an issue with the “everything’s an object” part, because it _is_ consistent, even though it gets a bit trippy when classes are objects as well and they are implementation of a Class class which is an implementation of itself (trickery again!). The issue is more with this part: >The `times` method exists on the Integer classes because doing something exactly an integer number of times happens a lot in practice. It is practical, but it breaks the conceptual model in that it is a hard sell that “times” is a property over the “5” object. The result is cleaner syntax, I know, but there is something in these choices that still feels continually “hacky/irky” to me.
- WJW 1y agoPerhaps 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.
- gray_-_wolf 1y ago> that “times” is a property over the “5” object Maybe here is the confusion, ruby is based on message passing, so the `times` is a message you are sending to 5, not a property of it.
- bigtunacan 1y agoI think you’re right, but I also suspect that doesn’t clear up anything for most people as in my experience they generally don’t grok the difference unless they’ve already spent a significant amount of time in something like smalltalk or Objective-C
- majormajor 1y agoWhen you're new it just looks like a weird-but-reasonable syntax. When you learn the language you really fall into two camps: - ah, yes, this is unusual, but it's consistent and now i understand the language - this is way too clever I'm more in the first camp.
- cortesoft 1y agoTimes isn’t a property of the 5 object, it is a method of the 5 object.
- kikimora 1y agoIn Smalltalk there is no if statement, but there is ifTrue and ifFalse methods in a Boolean variable. This is one example how to create custom control flow with non-local returns and blocks. Ruby follows suite.
- TOGoS 1y ago`5.times` is not so outlandish, though it would seem better for that to be in a `Loop` library or something (`Loop.times(5) { do some stuff }`). The `5.days` example that was posted somewhere else in this thread might be a better example. It is not, as far as I can tell, part of Ruby's core library, thank goodness, but it is the kind of thing the methods-as-hacks-for-syntax culture seems to encourage. My question being "why the heck should the number 5 know anything about days? What does 5.days even mean, given that there are various ways to interpret 'a day'?" This kind of bad design has made its way all over the place. The Java mocking libraries that my coworkers like to use are full of it. Long chains of method calls that appear like they're trying to 'look like English' but make no damn sense, so you have to dig into each method call and what kind of thing it returns to understand what this chain actually means.
- zem 1y agoyou can view it as serving the same role as universal function call syntax, the latter being a feature in some other languages that rewrites `a.f(x)` to `f(a, x)` if `a` doesn't have a method `f`. in ruby you can just add the method to `a`'s class directly, but the idea is the same - you're adding some user defined function that is closely related to the type of `a`, and you're using method call syntax because that reads nicely.
- layer8 1y agoWith similar justification, you could argue that blocks should have a method `times` taking an integer, because repeating a block an integer number of times happens a lot in practice. I’d even argue that it is conceptually closer to blocks than to integers, and therefore the method belongs on the block. Hence you’d write `{ puts “Hello” }.times 5`. But blocks are not objects in Ruby, so you can’t do that, and everything actually isn’t an object in Ruby. Also, it’s even more common to do something depending on whether a condition is true or false, but true and false in Ruby don’t have a method to (not) execute a block on them, and you use a non-OOP `if` instead, so what’s up with that?
- bashkiddie 1y ago> everything is an object, always. I beg to differ. What object does the method `puts` belong to? Why do you not call puts with its objects name? Ruby has a concept of mixins (Golang interfaces), these are not objects. Neither is `puts`
- kazinator 1y agoAnswerable by a few minutes of googling. Sort of: puts is a method which has a class: the Method class: irb(main):001:0> method(:puts) => #<Method: main.puts> irb(main):002:0> method(:puts).class => Method Everything being a confused muddle in Ruby, there is evidently some Kernel base class that is injected into every Object, and puts is a private method in that: irb(main):003:0> 3.puts() Traceback (most recent call last): 2: from /usr/bin/irb:11:in `<main>' 1: from (irb):3 NoMethodError (private method `puts' called for 3:Integer) The Method class of puts is a real class with methods and all: irb(main):004:0> method(:puts).class.methods => [:allocate, :superclass, :<=>, :<=, :>=, :==, :===, :autoload?, :autoload, :included_modules, :include?, :name, :ancestors, :attr, :attr_reader, :attr_writer, :attr_accessor, :instance_methods, :public_instance_methods, :protected_instance_methods, :private_instance_methods, :constants, :const_get, :const_set, :const_defined?, :class_variables, :remove_class_variable, :class_variable_get, :class_variable_set, :class_variable_defined?, :public_constant, :freeze, :inspect, :deprecate_constant, :private_constant, :const_missing, :singleton_class?, :prepend, :class_exec, :module_eval, :class_eval, :include, :<, :>, :remove_method, :undef_method, :alias_method, :protected_method_defined?, :module_exec, :method_defined?, :public_method_defined?, :to_s, :public_class_method, :public_instance_method, :define_method, :private_method_defined?, :private_class_method, :instance_method, :instance_variable_set, :instance_variable_defined?, :remove_instance_variable, :instance_of?, :kind_of?, :is_a?, :tap, :instance_variable_get, :instance_variables, :method, :public_method, :singleton_method, :define_singleton_method, :public_send, :extend, :to_enum, :enum_for, :pp, :=~, :!~, :eql?, :respond_to?, :object_id, :send, :display, :nil?, :hash, :class, :singleton_class, :clone, :dup, :itself, :yield_self, :taint, :tainted?, :untrust, :untaint, :trust, :untrusted?, :methods, :frozen?, :protected_methods, :singleton_methods, :public_methods, :private_methods, :!, :equal?, :instance_eval, :instance_exec, :!=, :__send__, :__id__]