Live data from Hacker News

The Dynamic Def – abusing Ruby's def statement

weblog.jamisbuck.org

61–70 of 87 posts

Re: The Dynamic Def – abusing Ruby's def statement

#61

Earlier quoted context omitted.

Very right. Operator overloading is a self-indulgent trick. The only one to benefit is the author. Subsequent readers are mostly confused. Instead of overloading, I've always wanted to define new operators e.g. used as 'int x = v1 v2;' If you're going to do something with operators, at least let me make descriptive ones.

I disagree, operator overloading often makes the code much more readable. I don't see the problem unless you're trying to write C in Ruby. As long as the overloaded implementation keeps the expected semantics as described in the language documentation, it's fine.

Most problems people have with operator overloading seem to be caused by the same issue that makes them whine about "debugging metaprogramming" (I referenced that in other comment here). Namely, instead of trying to understand the code and the model behind it, they try to bring over their own assumptions about how the code should work.

That 5-component object compared by ==? How does it work? Sit down, read the code and find out. The answer depends on what exactly the object represents and what makes sense in the domain model.

Re: The Dynamic Def – abusing Ruby's def statement

#62

Earlier quoted context omitted.

> Imagine a component with 5 attributes. Does '==' match them all? Some of them? Loosely or tightly? I'm afraid just seeing '==' in the code is never going to be informative. This is, fundamentally, a disagreement about the value of encapsulation. With an opaque, encapsulated type, '==' should mean whatever makes the most sense in the context of that type. For a pointer that might be "equality" means "same memory add…

Only if you do it that way. Make it MatchForParticularPurpose(v1, v2) instead, and voila no leak. Operators are unique in the language. They hold a special place. They deliberately are written to imply something we already understand. No fair lumping them in with every other attribute or method of an encapsulated type. Intuition is a very, very poor thing to depend upon in a programming language. I disagree heartily…

> Operators are unique in the language. They hold a special place. They deliberately are written to imply something we already understand. No fair lumping them in with every other attribute or method of an encapsulated type.

I would argue that a CORE value of Ruby is that everything is an object, and objects communication by message passing. Treating an operator as anything other than a message between objects is fundamentally wrong.

If you expect operators to do anything other than call the appropriate message on an object, you're misunderstanding the syntax.

Re: The Dynamic Def – abusing Ruby's def statement

#63

Earlier quoted context omitted.

Debugging Ruby, in general, is often a pain in the ass. It stems from an awful combination of terseness and metaprogramming. The terseness comes from using an identifier to both represent variable reference and method invocation. Contrast this to Lisp-like langauges, where function (or macro) invocation only happens in the first position of an S-expression. In Lisp, it's clear that an identifier is either a variable…

> Contrast this to Lisp-like langauges, where function (or macro) invocation only happens in the first position of an S-expression. In Lisp, it's clear that an identifier is either a variable or a function depending on where it's located. It may not be depending on the context; consider: (let ((foo 123) (+ 'please-dont-do-that)) (print foo) (print +)) Both `foo' and `+' are variables here - while present on a first p…

The example you gave is clear, though. They are variables because they appear in a LET form. It's clear from the context what is going on. What I'm referring to, in Ruby, is something like:

   def some_method
      what_is_this
   end
You don't know what what_is_this is. It could be an instance variable, a method (anywhere in the inheritance tower), or an autogenerated method from method_missing. It's impossible to tell without digging through the code. But the problem there is, you can't simply grep the code for things like this. You could end up with 100s of uses of the identifier and never find the source. Especially if you inherit a class from a gem, or method_missing has been used.

> In Lisp-n (like Common Lisp) you can have a variable slot bound to a function value (i.e. lambda).

Yes, and Lisp-1 vs Lisp-n has been a hot debate for decades in both Lisp and Scheme communities. I'm not about to claim it's a completely solved problem there.

Re: The Dynamic Def – abusing Ruby's def statement

#64

Earlier quoted context omitted.

Only if you do it that way. Make it MatchForParticularPurpose(v1, v2) instead, and voila no leak. Operators are unique in the language. They hold a special place. They deliberately are written to imply something we already understand. No fair lumping them in with every other attribute or method of an encapsulated type. Intuition is a very, very poor thing to depend upon in a programming language. I disagree heartily…

> Operators are unique in the language. They hold a special place. They deliberately are written to imply something we already understand. No fair lumping them in with every other attribute or method of an encapsulated type. I would argue that a CORE value of Ruby is that everything is an object, and objects communication by message passing. Treating an operator as anything other than a message between objects is fun…

Indeed, in a very real sense Ruby simply doesn't have "syntactic operators" in the C-ish sense he seems to regard them.

Ruby only has messages. Some of those messages just happen to have punctuation for names.

Re: The Dynamic Def – abusing Ruby's def statement

#65

Earlier quoted context omitted.

> Contrast this to Lisp-like langauges, where function (or macro) invocation only happens in the first position of an S-expression. In Lisp, it's clear that an identifier is either a variable or a function depending on where it's located. It may not be depending on the context; consider: (let ((foo 123) (+ 'please-dont-do-that)) (print foo) (print +)) Both `foo' and `+' are variables here - while present on a first p…

The example you gave is clear, though. They are variables because they appear in a LET form. It's clear from the context what is going on. What I'm referring to, in Ruby, is something like: def some_method what_is_this end You don't know what what_is_this is. It could be an instance variable, a method (anywhere in the inheritance tower), or an autogenerated method from method_missing. It's impossible to tell without…

Oh yeah. I see what you're complaining about and this kind of thing is indeed incredibly annoying.

Re: The Dynamic Def – abusing Ruby's def statement

#66
post #14

I've somehow always found Ruby to be opposite to the Unix philosophy of doing one thing and doing it well. While Ruby may seem trivial and fun in the beginning, it tends to be cumbersome and maintainable as the size of the repository grows. Coming from a Python world, my first reaction to Ruby was that it was more like Perl where there are many ways to achieve the same thing, and no it was not really helpful if you i…

> opposite to the Unix philosophy Bash: $ foo() { bar () { echo I am bar } } $ bar bar: not found $ foo $ bar I am bar Pretty much the same thing as the Ruby example. This is simply because function defining is a kind of statement or expression with a side effect which must be evaluated. The side effect is global (a name is globally associated with a function). So if the side effect is in a function body, its evaluat…

This is indeed the basic feature of any language that's evaluated at runtime. When working with such a language, one needs to learn the program as a dynamically growing construct instead of a vision cast in stone when you press the "compile" button.

Re: The Dynamic Def – abusing Ruby's def statement

#67
post #9

Earlier quoted context omitted.

Can you give me an example of where redefining equality makes sense?

Without explicit comments/documentation it is hard to imagine a good example. Its a longstanding issue. Even Lisp has 'equals' and 'equals?' which is an abomination. One looks for identity of object; the other for identity of value (if I understand it right). These kind of things are bug factories.

Lisp does not have 'equals' and 'equals?'.

Lisp has 'eq', which is for object identity. Lisp has 'equal' which checks for structural equality.

Re: The Dynamic Def – abusing Ruby's def statement

#68

Earlier quoted context omitted.

> Contrast this to Lisp-like langauges, where function (or macro) invocation only happens in the first position of an S-expression. In Lisp, it's clear that an identifier is either a variable or a function depending on where it's located. It may not be depending on the context; consider: (let ((foo 123) (+ 'please-dont-do-that)) (print foo) (print +)) Both `foo' and `+' are variables here - while present on a first p…

The example you gave is clear, though. They are variables because they appear in a LET form. It's clear from the context what is going on. What I'm referring to, in Ruby, is something like: def some_method what_is_this end You don't know what what_is_this is. It could be an instance variable, a method (anywhere in the inheritance tower), or an autogenerated method from method_missing. It's impossible to tell without…

   def some_method
      what_is_this
   end
> You don't know what what_is_this is. It could be an instance variable, a method (anywhere in the inheritance tower), or an autogenerated method from method_missing. It's impossible to tell without digging through the code.

We know it's not an instance variable -- that would be @what_is_this. It would have to be a local variable, but plainly there is no such variable local to this method. So we know it's a method. Let's go hunting (we could do this vis binding.pry, or byebug, or in IRB with an instance of whatever defined some_method):

    method(:what_is_this) rescue false # if false, this is coming from method_missing
    method(:what_is_this).source_location # there's your definition location, if it wasn't coming from method_missing

In general I used to find debugging ruby hard, coming from a background in more static languages. Once I learned the debugging facilities it provides, finding things got a lot easier.

Re: The Dynamic Def – abusing Ruby's def statement

#69
post #36

Earlier quoted context omitted.

Very right. Operator overloading is a self-indulgent trick. The only one to benefit is the author. Subsequent readers are mostly confused. Instead of overloading, I've always wanted to define new operators e.g. used as 'int x = v1 v2;' If you're going to do something with operators, at least let me make descriptive ones.

Ehh, there are good and bad uses. Yes, dot product should not be overloaded asterisk operator because elementwise multiplication of vectors is a thing, and also if you overload multiplication for matrices then dot product should be u-transpose-times-v for consistency. But what about vector addition? There's no ambiguity. Try writing any 3d graphics code without overloaded vector addition. It sucks. I generally disagr…

> Edit: I do think it's stupid to make the identity-equals operator overloadable. Identity-equals and value-equals are separate concepts. In C++ this isn't an issue because == is the value-equals operator.

In Ruby, == is value-equals at well (identity-equality being such a rarely needed concept in Ruby that it wouldn't make sense to privilege it with its own operator).

Identity equality in Ruby is provided by:

    a.object_id == b.object_id

Re: The Dynamic Def – abusing Ruby's def statement

#70

Earlier quoted context omitted.

> Contrast this to Lisp-like langauges, where function (or macro) invocation only happens in the first position of an S-expression. In Lisp, it's clear that an identifier is either a variable or a function depending on where it's located. It may not be depending on the context; consider: (let ((foo 123) (+ 'please-dont-do-that)) (print foo) (print +)) Both `foo' and `+' are variables here - while present on a first p…

The example you gave is clear, though. They are variables because they appear in a LET form. It's clear from the context what is going on. What I'm referring to, in Ruby, is something like: def some_method what_is_this end You don't know what what_is_this is. It could be an instance variable, a method (anywhere in the inheritance tower), or an autogenerated method from method_missing. It's impossible to tell without…

> But the problem there is, you can't simply grep the code for things like this. You could end up with 100s of uses of the identifier and never find the source.

Personally, I see this as the entire point of the original Smalltalk-esque OOP paradigm: an object is a living, mutating black box that responds to messages, where an object's "type" is just "object": a thing with a protocol for sending and receiving arbitrary messages, not a thing with particular messages it is lexically known to respond to.

In other words, an object is like a remote server on the Internet. You can no more introspect an object by looking at the source of the classes it was originally constructed from, than you can introspect a remote web service by reading the source of the frameworks it was based on. The set of messages an object will respond to, and how it will respond to them, is part of the object's state, not part of its definition.

This "absolute encapsulation" forces the producer to publish an API if they want anyone to consume their object/service. This is great! If the API is machine-readable, and the object/service publishes it as a response to a message, an OOP runtime can even perform runtime introspection on the object/service, enabling consumers to configure themselves to speak another object's protocol, to reconfigure when that protocol changes, and even for two objects to negotiate a communications protocol among many options.

People have rediscovered the benefits of black-boxes with published, oft-machine-readable APIs in the last few years, calling the new version of OOP "microservices." It's the same idea: objects with no lexical type beyond "thing that responds to messages encoded in this format", being used as black-box interfaces into libraries which may be running locally or remotely.

This is also, effectively, the definition of a "process" in Erlang: something that will (asynchronously) receive messages if you send them, and might send you a response, or might not, with the interpretation of a given message depending entirely on the process's state. (People who say Erlang is a functional language are looking on the wrong level of abstraction. Processes are objects!)

---

To get back to Ruby, though: imagine an object which serves as a REST client, where the method_missing of that object translates the {method_name, ∗args} into a GET request to an API server, with the method name becoming the path and the arguments becoming query parameters. This is an idiomatic kind of Ruby object, because Ruby is actual-OOP rather than the "classes are types, right?" faux-OOP of static languages.

The messages this REST-client object responds to depend entirely on code running somewhere else that could be modified at any time. There is nothing static analysis tools could do to figure out what this object will or won't respond to. The only way to introspect its operation at all is at runtime.

And yet, I would argue that this implementation of such an object is the best, most 1:1 translation of the concept of "REST client" into a programming language. Errors are propagated from the remote server, through HTTP, into local dynamic-dispatch errors, through the defaults of Ruby's runtime. You're not having to go against the grain, writing a "get" method and then making up all sorts of custom exceptions it can raise. You just make a local object, that stands in for a remote object, and then you interact with it as an object.

Post reply on HN