Methods That Self-Destruct #
Expiring a method. And let’s keep meta out of this.
class Trial def run_me def self.run_me; raise Exception, "NO MORE." end puts "Your trial period has ended." end end t = Trial.new t.run_me #=> Your trial period has ended. t.run_me #=> (trial):3:in `run_me': NO MORE. (Exception)
The run_me
overwrites itself. But notice it uses def self
. This overwrites the method in the object, not in the class. So you can create more Trial
objects which haven’t self-destructed yet.
Can also be used as a memoization technique. But, rather than caching the data, you just ensure that the code runs only once.
class Hit def initialize(ip) @ip = ip end def country def self.country; @country end @country = `geoiplookup #{@ip}`.chomp.gsub(/^GeoIP Country Edition: /,"") end end
I should correct one thing. I’m not actually overwriting the method. Just capping it off with a new singleton method.
For more on singleton methods: an intro and some old thoughts on duck-typed singletons.
Oy: Don’t miss MenTaL’s method-rewriting state machine. Sixth comment down, that big one.
MenTaLguY
Yeah—this technique is simple, but really underappreciated.
However, if you’re using it to optimize
@country ||= ...
, it’s worth noting that the following technique creates a considerably faster accessor:A benchmark of 5,000,000 reads comes out like:
@variable ||=
def self.accessor
class << self ; attr_reader
As you can see,
attr_reader
wins by quite a bit.hoyhoy
class Fixnum def + (a) case rand 4 when 0 def + (b); self+b end when 1 def + (b); self*b end when 2 def + (b); self-b end when 3 def + (b); self/b end end end end
hoyhoy
trans
Hmm… is this better than the traditional form of memoization? Or does it have adverse effects by comparison?
Also, I’m still looking for a way to task dependencies with parameters just by calling them like methods at the beginning of the task def—I wonder if this would work?
MenTaLguY
trans: it can be bad with inheritance—e.g. if somebody derives from
Hit
and overridesHit#country
, you will clobber that. Otherwise, singleton-methods-for-memoization is very win.MenTaLguY
It’s worth nothing that self-replacing methods aren’t only for destruction, memoization, and messing with people who are fond of
Fixnum#+
(I think that is most of us)—you can use them to maintain state. For example, here’s a state machine which recognizes a sequence likea(bc)*(a|c)
:This is an objectless embodiment of the State pattern, in much the same way that Ruby iterators are an objectless embodiment of the Visitor pattern.
why
MenTaLguY: Thanks for the benchmark. That’s really nice to know.
hgs
Since when have we been able to have
def...end
directly insidedef...end
, without a class definition nested between? I’ve not attempted this for a while but when I last tried it, I got a error. I concluded there was a reason for this by design, and left it there. Now, even with 1.8.2 I can. So, do we need define_method any more?MenTaLguY: object-less State pattern: Nice!
I feel pulled two ways: this is powerful and the mechanism is clear, but it is self-modifying code, which one is taught to avoid. Or do I need to unlearn that? After all, goto is OK in the right circumstances…
pHiL
hgs: you need to unlearn all that. Otherwise you’ll never do any metaprogramming. That rule against self-modifying code was for a static world. We now are boldly entering a new dynamic world and _why is leading the way! Onward to the glorious, dynamic future!
...OK, just don’t get too carried away with it. Use judiciously. Proceed with caution but tread with confidence. Remember: self-modifying code can smell fear – never let it know you’re afraid and you’ll be fine. A bit like lion taming in that regard.
hgs
Yes, good point about parallels with metaprogramming. And fear is the mind-killer, of course :-)
MenTaLguY
You’re welcome! However, I got curious how memoized reads compare to the one-time setup overhead. This is what I found (times are normalized):
read
||=
def self.accessor
class << self ; attr_reader
This means that it takes a few calls to “break even”, and a few more before one of the “faster” techniques finally wins:
||=
def self
attr_reader
I think there’s probably a lesson here…
.
Do you really need the self. here? Wouldn’t
def runme def runme; end end
Work just the same?
MenTaLguY
hgs: you’ve always (as far as I know) been able to do a
def obj.meth
outside a class or module, just never a regular def. The rationale: the receiver of the method definition is explicit when you’re defining a singleton method on an object, but if you’re outside a class or module and saydef meth
, where does it go?self.class
isn’t always a desirable answer for that, nor is “the class/module on which the enclosing method was defined”...MenTaLguY
.: it’s not allowed syntax. If it were, I’m not sure people could agree on what it should mean.
why
MenTaLguY: Welp, I guess the lesson is:
MenTaLguY
pHiL: I think the thing to ask is always “does doing it this way make the code clearer?”
shadytrees
His syntax works for me without warning or error on Ruby 1.8.4.
With one difference:
def praise
overwrites the method. That is, the output is one “Good boy” and five “Bad boy” instead of two “Good boy” and four “Bad boy,” which is whatdef self.praise
does.Fun.
More proof cats are for the wins and dogs are not.
MenTaLguY
why: Dude, no, it’s got to be something like this:
MenTaLguY
shadytrees: Wow, okay, see—I am very good at coming up with rationalizations for things which are totally wrong.
So, playing with it, the answer to where the method goes appears to be “the lexically innermost enclosing class/module block at the site where the method was defined, or Object if there is no such enclosing block”.
That means you have to be careful with def inside of a
def obj.meth
versus a def inside a def inclass << obj
. The former will put the method in the lexically enclosing class, not the singleton class ofobj
.nick
Nice timing, I just wrote up an article on the same thing, but alas with much less brevity and wit :(
drbrain
attr_accessor and friends are faster than real methods because they throw out the scope:
nonni
How does one look up old ruby blog articles on whytheluckystiff as per some old thoughts…?
hgs
nonni: Googling with the expression
site:whytheluckystiff.net
along with your other keywords will probably help until a better answer appears.