
The Dynamic Def – abusing Ruby's def statement - jamis
http://weblog.jamisbuck.org/2015/10/17/dynamic-def.html
======
bascule
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](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.

~~~
byroot
It was merged for MRI 2.1: [https://bugs.ruby-
lang.org/issues/8426](https://bugs.ruby-lang.org/issues/8426)

~~~
bascule
That change was reverted: [https://bugs.ruby-lang.org/projects/ruby-
trunk/repository/re...](https://bugs.ruby-lang.org/projects/ruby-
trunk/repository/revisions/43027)

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

~~~
byroot
I insist, it's mostly solved: [http://tmm1.net/ruby21-method-
cache/](http://tmm1.net/ruby21-method-cache/)

------
wdewind
Cool article about the craziness of Ruby. Ruby is a frustrating language. The
oauth gem, for instance, redefines '==(val)' on the AccessToken to
'Base64.encode(self.signature) == Base64.encode(val).'

This stuff feels really dangerous and unnecessary. I spend a lot of time on
code reviews pointing out bad features of Ruby (and Rails) that we shouldn't
be using because they break application flow and make it significantly harder
to reason about the code for the small benefit of decreasing a few lines. But
it's certainly fun to talk about :)

~~~
regularfry
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
doesn't behave like you expect. It's just another bit of mental trivia you've
got to Just Know...

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

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

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

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

------
TheAceOfHearts
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!

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

~~~
TeMPOraL
> _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.

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

~~~
msbarnett

       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.

~~~
girvo
Right. Once you understand how to use run-time tooling, debugging dynamic
languages becomes rather straightforward.

~~~
TeMPOraL
True. You have to learn to treat programs written in dynamic languages as
living, mutable, interactive things instead of designs set in stone. However,
when a simple syntax derails your reading, it is a thing of concern.

Then again, you could make similar shenanigans in Common Lisp with `symbol-
macrolet', but it's obscure feature that pretty much by definition will be
used only by people who know when to use it :).

~~~
girvo
> _However, when a simple syntax derails your reading, it is a thing of
> concern._

Definitely agreed! It's rare that I've come across a set of code that is _so_
over-abstracted or dynamic that reading the code doesn't usually shed light on
the issue I'm after, and usually it's a "dense" language (Scala, Lisps, etc.)
that manages to achieve that.

~~~
TeMPOraL
Last time I was really, seriously confused about the code was when going
through a Scala codebase written by a person deep in love with traits. My
problem was less of the syntax or "denseness" and more of nonlocality - every
conceptually "whole" algorithm was split into 10 or 20 files and 2 class
hierarchies, each having their own hierarchy of traits.

It made some sense in the end, but it took me _a lot_ of time to figure that
one out - not because it was complicated, but didn't fit in my head. That the
original author was uncooperative and didn't want to explain things too much
didn't help either.

------
zwp
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 more neatly achieved
with subclassing. [To the extent of faking anything "immutable" in ruby"].

There's #freeze I suppose... and no #unfreeze. Which is perhaps sensible :)
And #freeze only guards against new assignment to instance variables; it
doesn't guard against an instance method mutating the content of an instance
variable (def esquirify! ; @name << ' Esq.' ; end). So there's that. But I'm
far from convinced...

Python has this "inner-methods" capability (with saner scoping) which is a
great antidote for python's limited lambdas. But that's not a problem with
ruby.

It's a neat trick (and nice to read something from Jamis again). Are there any
sensible use cases?

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

~~~
netghost
Yup, here's how to use it to define a Singleton that is transparent to the
caller:

    
    
        class S
          class << self
            attr_accessor :singleton
          end
          
          attr_accessor :value
          
          def initialize
            @value = "xxx"
            S.singleton = self
            def S.new
              S.singleton
            end
          end  
        end
        
        a = S.new
        # => #<S:0x007ff4a41532e0 @value="xxx">
        b = S.new
        #=> #<S:0x007ff4a41532e0 @value="xxx">
        a.value = "zzz"
        b.value # "zzz"
         
        a.object_id == b.object_id
        #=> true
    

That said... doing this may win you great sorrow.

------
qewrffewqwfqew
For a much deeper exploration of this kind of dynamic object behaviour in Tcl,
Sean Woods' "Lifecycle Object Generators" is a fun read:

[http://www.tclcommunityassociation.org/wub/proceedings/Proce...](http://www.tclcommunityassociation.org/wub/proceedings/Proceedings-2012/SeanWoods/Lifecycle-
of-Objects.pdf)

------
archimedespi
Ha! An IRB-based interactive adventure is actually _really, really_ cool and
clever. It never fails to amaze me how much Rubyists abuse metaprogramming and
langauge quirks.

Somebody now shall goeth forth and implement Zork...

~~~
jamis
Thanks! I'm not the first to talk about using IRB for interactive fiction, but
I think I might be the first to do so using nested defs. :) IRB-based Zork
would be awesome! I hope someone does that.

------
theyeti
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 inherited poorly written code.

~~~
kazinator
> _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.

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

~~~
kazinator
Or rather, one needs to learn which constructs destructively manipulate a
global environment, and which perform lexical binding.

In Python, an inner def will lexically bind a function, creating a closure.
Python is not less dynamic than Ruby.

In Common Lisp, a defun inside a defun will behave similarly to Ruby; but if
you want lexically scoped local functions, you use a different operator,
namely flet or labels.

Scheme has a define which is lexical: it brings a lexical identifier into the
scope for forms which follow.

Lexically scoped items, even in a dynamic language, in fact _can_ be "cast in
stone when you press the compile button"; they are cast in that stone which is
the entire compiled environment of the surrounding function.

------
regularfry
So, about that state machine definition and the method cache...

~~~
Freaky
Or the JIT. We're not all using MRI.

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

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

~~~
seivadmas
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...](https://github.com/plataformatec/devise/blob/master/lib/devise/controllers/helpers.rb)

