Live data from Hacker News

The Dynamic Def – abusing Ruby's def statement

weblog.jamisbuck.org

41–50 of 87 posts

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

#41
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 evaluation is delayed until the function is called, and then its effect is still global.

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

#42

Earlier quoted context omitted.

> Readability > cleverness every single time. Readability is in the eye of the beholder. For those unfamiliar with higher-order programming, maps are just a 'too clever' form of a for loop. A clean and understandable solution is one matching the problem being solved in a precise way. In search for simplicity one can't forget that programming is a trade , and one should be expected to actually learn some shit.

To that point, Jef Raskin famouly said that “intuitive == familiar,” and all-too-often, that’s exactly what people mean when they talk about “intuitive" code and/or user interfaces.

Indeed. Looking over the article, the first example is sort of obvious if you ever worked more than few hours with a decent dynamically typed language, and the rest expose interesting functionality that could be papered over with a macro in order to build something useful. Like, you know, object-oriented programming can be built by hiding lexical closures under a macro or two, and was in fact built that way in the past.

In other words - just because you don't understand something doesn't mean it's "clever code".

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

#43

Just because you CAN do something doesn't mean you SHOULD. Metaprogramming has a place in Ruby but for the examples in the article there are far more readable ways to implement it. Readability > cleverness every single time.

This is a fundamental part of the language, the idea that objects and classes are dynamic, not static. What I would say is that although we should have an extremely good reason to employ these techniques in production, I believe that every professional Ruby programmer should be able to understand them and/or figure them out in a few minutes. It’s not like we’re talking about obfuscated C.

Agree with you 100%, and the ability to dynamically define methods is one of Ruby's great strengths.

However, metaprogramming is a power that should be used wisely. When implemented unnecessarily it reduces readability (and probably performance) for no real gain.

The devise source contains some great examples of metaprogramming used properly:

https://github.com/plataformatec/devise/blob/master/lib/devi...

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

#44
post #9

Earlier quoted context omitted.

In its place, being able to redefine equality on value types really helps clarify code. Misused, it creates confusion. The problem is that it's easy to think you've got a case where it helps, when actually it's not well-defined. Usually that revolves around there being state you care about which is missed from the equality comparison. Another trap is redefining #== without also looking at #eql?, which means Hash does…

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

Another (even more simple than Money) example are many of the standard library classes.

For example BigDecimal. With operator overloading, I can easily compare a BigDecimal object to an integer or float, the same way I can already compare them to each other:

    BigDecimal.new("10.0") == 10.0
    10 == 10.0
Without operator overloading, this would become needlessly messy (and require explicit handling of nils):

    d = BigDecimal.new("10.0")
    !d.nil? && d.value_equal_to(10.0)
Ruby has always been about readability of the code, and avoiding unnecessary repetition, and I think operator overloading (when used correctly) is a great example of this.

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

#45

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!

I don't see anything hard to debug in this code. Honestly, if this kind of code makes debugging hard for one it means that one assumes too much about how code should behave instead of looking how it actually behaves.

I hate this "dynamic programming[0] = hard to debug" meme. A truly hard to debug code is one that's nonlocal (you have to read 20 files to follow the execution flow (hello Scala trait abusers)) or displays random behaviour (e.g. threading, dependence on external resources). This one? Every "tricky" thing is contained in a block of 10-20 lines. Just isolate the endpoints and follow the execution until it does something you think it shouldn't.

In a way, the biggest enemy of a bug hunter is their own assumptions.

[0] - examples here aren't really metaprogramming, and even the latter isn't that hard to debug if you actually sit down and read the code.

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

#46
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…

Touché

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

#47
post #20

Zork-alikes aside... I'm looking for practical applications :) I recently re-watched Yaron Minsky's "Effective ML" talk where (towards the end) he talks about making read-only and read-write types. That's where I thought Jamis was going with the state machine example: one could tell an object "yo, make yourself immutable!" (ie redefine your methods so that you can't change yourself). But in an OO world that seems mor…

One use might be to create simple singletons. That said, I'm not entirely sure how you would do it, maybe have `Object#initialize` redefine `Object#new` to always return the singleton.

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

#49
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')`.

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 wouldn't be equal. Why? For the same reason `1 != "1"`, if you cast them, yes they are equal, but implicit casting (aka weak typing) is not idiomatic in Ruby, it's possible, but very rare.

> boolean currenciesHaveSameWorth(Money m1, Money m2)

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

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

#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 techniques discussed in the OP in production code or in your gems. It may make your memos 30% faster on a microbenchmark... while causing the rest of your program to run considerably slower.

Post reply on HN