Live data from Hacker News

The Dynamic Def – abusing Ruby's def statement

weblog.jamisbuck.org

51–60 of 87 posts

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

#51

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…

> Only if you do it that way. Make it MatchForParticularPurpose(v1, v2) instead, and voila no leak.

Making the name more opaque won't save you at all. You're making what should be a local detail -- how your type implements equality -- into something that only works when it is global knowledge.

Consequently, your type is brittle and incomposable with types that aren't infected with this knowledge:

    [instance11, instance2, instance3].sort
won't do anything sensible until we infect either the Array type or the call site with non-local knowledge about your type.

Every type you build in this manner will find knowledge about itself diffusing throughout your application, like children peeing in a pool. Composition will be limited, inflexible, and require manual insertion of type-specific knowledge, because you have failed to encapsulate knowledge about equality.

Everything needs to know about everything else, and in the end you've built a tightly coupled ball of mud.

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

#52

Earlier quoted context omitted.

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.

I think Python make the difference clear, with == vs "is".

Ruby provides object_id for the same purpose. Comparing those for equality provides the "is_" semantics.

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

#53

Ruby is really fun to program in, but debugging it can be hell. I would not be very enthusiastic about debugging this code. But still, this is a really cool trick that I didn't know you could pull off with Ruby, so thanks for sharing!

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 or a function depending on where it's located.

In Ruby, no visual indication exists. Worse, things like attr_reader blend instance variables with local variables and method identifiers. Throw in a method_missing and inheritance, and you can easily lose weeks just tracking down where an identifier is even coming from. Throw in a gem or two, and all hope is lost.

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

#54
post #49

Earlier quoted context omitted.

Should Money.new(20, 'USD') == Money.new(2000, 'US Cents')? Or Money.new(20, 'USD') == Money.new(125, 'CNY') when ExchangeRateManager.getExchangeRate('CNY', 'USD') == 0.16? My point is that when performing these comparisons, it may be useful to use a more descriptive function like: boolean currenciesHaveSameWorth(Money m1, Money m2) And then a reader of the calling code might not have to look into the implementation…

> Should Money.new(20, 'USD') == Money.new(2000, 'US Cents')? Like someone else pointed out in this thread, designing APIs require consistency and good taste. If I had to implement this API you code above would evaluate to `ArgumentError unknown currency "US Cents"`. > Or Money.new(20, 'USD') == Money.new(125, 'CNY') when ExchangeRateManager.getExchangeRate('CNY', 'USD') == 0.16? Again, me designing this API, it woul…

> At this point you might as well do `m1 == m2.convert_to(m1.currency)`, because "HaveSameWorth" might mean many different things too.

I personally hate that last style because it's obvious that the "HaveSameWorth" relation is intended to be symmetric, and by writing it like m1 == m2.convert(...) you're prefering one side over the other. It looks bad for me :).

Also, in case of real-life objects it makes sense to spell out what do you mean by 'equality' (or 'equivalency'), and leave the default implementation to represent the philosophical concepts of "the same" and "equivalent to".

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

#55
post #50

Defining instance-specific behavior of any kind is catastrophic to method caching. JRuby has a hierarchical method cache so it can clear only what's needed, but MRI does not: http://jamesgolick.com/2013/4/14/mris-method-caches.html The late, great James Golick had a patch to add one once, but it never got merged upstream. If you care about performance even the tiniest bit at all whatsoever, please don't use the techn…

It was merged for MRI 2.1: https://bugs.ruby-lang.org/issues/8426

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

#56

Ruby is really fun to program in, but debugging it can be hell. I would not be very enthusiastic about debugging this code. But still, this is a really cool trick that I didn't know you could pull off with Ruby, so thanks for sharing!

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 position of an S-expression at one point - even if you're running a Lisp-1 (like Scheme), i.e. where functions and variables share a namespace - or rather, a symbol can have separate function and value bindings. In Lisp-n (like Common Lisp) you can have a variable slot bound to a function value (i.e. lambda).

But one thing Lisp does have, which impacts readability significantly IMO, is simple and consistent syntax. Contrast to some Ruby-like languages which let you skip braces when working with dictionaries, making you stop and wonder how the hell a given piece of code is going to be parsed by the interpreter. Or Scala, which has so much context-dependent meaning bound to non-letter characters that I finally start to understand why people were afraid of C++ operator overloading. Both examples are, in my opinion, cases of syntactic sugar leading to cancer of semicolon.

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

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

Kent Pitman has always been a good read, for the problems of equality in dynamic languages. The typical Ruby or JS programmer stumbles through the day just "getting by", where it comes to comparing objects.

http://www.nhplace.com/kent/PS/EQUAL.html

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

#58
post #49

Earlier quoted context omitted.

> Should Money.new(20, 'USD') == Money.new(2000, 'US Cents')? Like someone else pointed out in this thread, designing APIs require consistency and good taste. If I had to implement this API you code above would evaluate to `ArgumentError unknown currency "US Cents"`. > Or Money.new(20, 'USD') == Money.new(125, 'CNY') when ExchangeRateManager.getExchangeRate('CNY', 'USD') == 0.16? Again, me designing this API, it woul…

> At this point you might as well do `m1 == m2.convert_to(m1.currency)`, because "HaveSameWorth" might mean many different things too. I personally hate that last style because it's obvious that the "HaveSameWorth" relation is intended to be symmetric, and by writing it like m1 == m2.convert(...) you're prefering one side over the other. It looks bad for me :). Also, in case of real-life objects it makes sense to spe…

But you almost certainly are logically preferring one side over the other! You do usdValue == cadValye.convert_to(usdValue.curr) because one of those currencies is the one your transaction is working with (in this case, USD is your goal).

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

#59
post #55
post #50

Defining instance-specific behavior of any kind is catastrophic to method caching. JRuby has a hierarchical method cache so it can clear only what's needed, but MRI does not: http://jamesgolick.com/2013/4/14/mris-method-caches.html The late, great James Golick had a patch to add one once, but it never got merged upstream. If you care about performance even the tiniest bit at all whatsoever, please don't use the techn…

It was merged for MRI 2.1: https://bugs.ruby-lang.org/issues/8426

That change was reverted: https://bugs.ruby-lang.org/projects/ruby-trunk/repository/re...

It remains an open issue: https://bugs.ruby-lang.org/issues/9262

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

#60
post #16
post #12

Earlier quoted context omitted.

Anytime you have some value type. Say a `Money` class for instance. The default `==` from Object compare identity, so unless you define it `Money.new(20, 'USD') != Money.new(20, 'USD')`.

Right, this seems like exactly why we shouldn't be allowed to redefine it. As a reader of your code that redefines ==, I think == means we are talking identity until I find your function that redefines ==. It has made it so I need to understand more things in order to be able to reason about your code. That seems like a negative to me.

If somebody wants object identity rather than semantic equality, they should be using `equal?`. The fact that different types have different equality semantics if just kind of inherent in the idea of a type.
Post reply on HN