Comment by WJW

Comment by WJW 20 hours ago

35 replies

> 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.

kace91 20 hours ago

I 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 20 hours 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).

    • kace91 19 hours ago

      Maybe 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.

      • bigtunacan 16 hours ago

        It’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.

      • oezi 19 hours ago

        I 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.

  • gray_-_wolf 20 hours 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 17 hours ago

      I 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 13 hours ago

    When 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 10 hours ago

    Times isn’t a property of the 5 object, it is a method of the 5 object.

layer8 12 hours ago

With 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 10 hours 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`

  • chao- 10 hours ago

    >Ruby has a concept of mixins (Golang interfaces), these are not objects.

    Ruby "mixins" are the affordance of sharing instance methods from a module via a keyword. Modules are objects, and are instances of class Module:

      module ProvidesFooMixin
        def foo = "foo"
      end
    
      class Bar
        include ProvidesFooMixin
      end
    
      Bar.new.foo
      # => "foo"
    
      puts(ProvidesFooMixin.class)
      # => Module
    
      ProvidesFooMixin.object_id
      # => (some integer value)
    
    >Neither is `puts`

    Like all methods, `puts` is an object:

      method(:puts)
      # => #<Method: Object(Kernel)#puts(*)>
    
      method(:puts).class
      # => Method
    
      method(:puts).object_id
      # => (some integer value)
    
    Here you see evidence of where `puts` comes from: Kernel#puts via Object, which I will now explain in detail.

    >What object does the method `puts` belong to?

    It belongs to the object you are calling it from within. You don't need to call `puts` with a receiver because it is an instance method, just like you don't need to call an instance method `foo` via `self.foo`. But you could choose to use a receiver, since the `puts` you know and love is just another instance method. You can try `self.puts` for yourself in some context!

    Your classes (and their instances) inherit this `self.puts` instance method from the Object class, which includes the Kernel module, which provides `Kernel#puts`. So the only reason you can send it as a message without a receiver is because it is just another instance method (again, the same as calling instance method `#foo` without using `self.foo`).

    Caveat: You can build an "alternate universe" object hierarchy by inheriting from BasicObject, and in your alternate universe, you can choose to not `include Kernel`, and you will see that instances of your new objects do not have access to `puts` in their instance scope.

  • WJW 9 hours ago

    `puts` is just a method of the Kernel module: https://ruby-doc.org/3.4.1/Kernel.html#method-i-puts, just like `p` and many others. Kernel is included in the Object class that is the root of the class hierarchy, so its methods are available in every ruby object.

    Mixins are just modules, which are objects, which you can call methods on. (Or rather, send messages to) You can easily verify this in irb calling a method on (for example) the Enumerable module:

        irb(main):001> Enumerable.class
        => Module
    
    You are right that a module is not a class, and it is not possible to call `.new` on it. But the module itself is very much an object.
  • cortesoft 10 hours ago

    Well, “puts” is a method defined in the Kernel module, which is included in the Object class, which means it is available in all contexts (since every object is a subclass of the ‘Object’ class). So, to answer your question, the puts method belongs to the Kernel module object, and the Kernel module object is an instance of a Module object.

  • kazinator 10 hours ago

    Answerable 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__]
    • cortesoft 10 hours ago

      > Everything being a confused muddle in Ruby

      It really isn’t a confused muddle, the rules are very clear. Just because it doesn’t match what you expect from your other language experience doesn’t mean it isn’t clear.

      • pxc 8 hours ago

        I'm not a Rubyist really, but I started using it at work a few weeks ago for a very small script. I'm not allergic to RTFM, so I picked up the canonical reference book on Ruby and occasionally visit the official docs for the language. I agree: the structure is clear and straightforward and there's nothing difficult about learning it.

        It's also the opposite of magic; magic is when language features can't be described in terms of the language itself.

      • kazinator 5 hours ago

        Clear, like the glass in Liberace's candelabra.

  • jfabre 10 hours ago

    Modules (mixins) are objects, classes are also objects (of type Class).

    The Kernel module is included in the Object class, which means its methods are available to every Ruby object and can be accessed from virtually any scope, including the top-level (global) context where self is an instance of Object called "main."

  • [removed] 7 hours ago
    [deleted]
  • pxc 9 hours ago

    > I beg to differ. [...] Why do you not call puts with its objects name?

    In the first place, I'd say what you're asking for goes beyond "everything is an object".

    But I think your questions can be answered in a way that affirms that "everything is an object" in Ruby anyway.

    > Why do you not call puts with its objects name?

    Because it belongs to whatever object you're working in already; `puts` is identical to `self.puts`. And yes, you're always working in an object: https://bparanj.gitbooks.io/ruby-basics/content/chapter1.htm...

    > What object does the method `puts` belong to?

    As indicated above, it belongs to the object `self`. It gets added object via the mixing-in Kernel module into Object. Kernel is itself an instance of class Module: https://docs.ruby-lang.org/en/3.4/Module.html

    The `puts` in Kernel delegates to `puts` from IO, which is likewise an instance method belonging to the object you can refer to by the name `$stdout`: https://docs.ruby-lang.org/en/3.4/IO.html

    > Ruby has a concept of mixins [and], these are not objects.

    Sure they are. Mixins themselves inherit from Module, and Modules are also objects (just like classes are).

    Some highlights from Chapter 27 ("Library Reference: The Class Model") of the recent edition of the pickaxe book (emphasis mine):

    > The Kernel module is included by class Object, so its methods are available in every Ruby object. One of the reasons for the Kernel module is to allow methods like `puts` and `gets` to be available everywhere and even to look like global commands. Kernel methods allow Ruby to still maintain an "everything is an object semantics".

    and regarding mixins:

    > The Module class is the class of any module you declare with the `module` keyword. Each module is an instance of class Module.

    on Object:

    > Object is the parent class of (almost) all classes in Ruby unless a class explicitly inherits from. BasicObject. [...] Object mixes in the Kernel module, making the built-in functions globally accessible.

    tl;dr: mixins in Ruby are instances of class Module, and their methods end up bound to instances of class Object. Abstract module methods that don't belong to a concrete instance of some class that mixed in their module belong to the Object that is the module itself (the instance of class Module). (The same kind of thing is how class methods work.)

TOGoS 14 hours 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 5 hours ago

    you 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.