Skip to content

Instantly share code, notes, and snippets.

@peterc
Created July 1, 2012 03:15
Show Gist options
  • Save peterc/3026651 to your computer and use it in GitHub Desktop.
Save peterc/3026651 to your computer and use it in GitHub Desktop.
Argument mutation detection in Ruby (rough experiment)
# encoding: utf-8
# This is a very early scrappy step in building an automatic memoizer (think IncPy)
# in pure Ruby. Is it possible? Who knows. A first step, then, is to rule out any
# methods that mutate their parameters.. which I attempt to do here.
# Added:
# Added clever define_method and 'store the old method in a closure' trickery
# from Caius Durling's fork at https://gist.github.com/3027213/f900b1ec8e04736267fe445607a4aeb3feea9b54
# Added:
# Now using Object#hash rather than a deep copied version of the arguments
module AutoMemo
CaughtMutation = Class.new(StandardError)
def monitor(meth)
unbound_method = instance_method(meth)
define_method(meth) do |*args, &blk|
old_hash = args.hash
unbound_method.bind(self).call(*args, &blk).tap do
btrace = (unbound_method.source_location + ["in `#{meth}'"]) * ':'
raise CaughtMutation, nil, btrace if args.hash != old_hash
end
end
end
def method_added(meth_name)
return false if self == Class || (@meths ||= {})[meth_name]
@meths[meth_name] = true
monitor(meth_name)
end
end
Class.send :include, AutoMemo
# --- the above would all be in a library/separate file that you required in first
# --- the below is 'normal' application code
class Adder
def add(a, b)
a.push("oh dear") # comment this out to 'fix' the problem
a + b
end
end
a = Adder.new
p a.add(%w{a b c}, %w{d e f})
@caius
Copy link

caius commented Jul 1, 2012

Had a go at writing a version that doesn't use class_eval or alias_method - https://gist.github.com/3027213 😄

@peterc
Copy link
Author

peterc commented Jul 1, 2012

Comment on your cleverness over at that gist - thanks! Implemented its main ideas into this as well as a new one of my own (using Object#hash instead of a deep copy).

@caius
Copy link

caius commented Jul 1, 2012

👍

@caius
Copy link

caius commented Jul 1, 2012

You can lose the check in method_added to see if the method name starts with __ as well now - as you're no longer defining __#{original_method_name} as the over-ridden method. Otherwise you're accidentally ignoring any methods anyone names as __* for other reasons - which may not be intentional?

@peterc
Copy link
Author

peterc commented Jul 1, 2012

Since this is just playing round / prototyping, I might. That said, in most cases I think __ methods are "special" enough to probably not be suitable for memoization (or memoization of them would cause confusion). I'll drop it for now though as it's not too important and having cleaner code is a bonus.

Ultimately, raising an exception is also not required - instead, it should just mark the method as not to be memoized.. but handy to see while fleshing out the idea for now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment