Skip to content

Instantly share code, notes, and snippets.

@cupakromer
Last active August 29, 2015 13:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cupakromer/8851663 to your computer and use it in GitHub Desktop.
Save cupakromer/8851663 to your computer and use it in GitHub Desktop.
On Open Classes and Message Passing in Ruby #retroruby
"Words" * 2
# => "WordsWords"
2 * "Words"
# TypeError: String can't be coerced into Fixnum
# Oops what just happened?
# In Ruby, virtually everything is a message to another object.
# Above we would say:
# Send the message * to "Words" with argument 2
# Send the message * to 2 with argument "Words"
#
# "Sending a message" is the Java equivalent of calling a method.
# Another way to write the above is:
"Words".*(2)
# => "WordsWords"
# We just used the 'method' syntax: obj.method(args)
# We could also write:
"Words".send(:*, 2)
# => "WordsWords"
# Explicitly send the message: obj.send(message, args)
# So back to the original issue. Both String and Fixnum respond
# to the message :*.
#
# String#* http://ruby-doc.org/core-2.1.0/String.html#method-i-2A
# Fixnum#* http://ruby-doc.org/core-2.1.0/Fixnum.html#method-i-2A
#
# However, String's implementation knows what to do when the
# argument is a Fixnum. When we reverse it, the Fixnum
# implementation doesn't understand what to do with a String
# argument.
# How to fix this?
# Well you probably shouldn't. But just for fun we'll use Ruby's
# open class behavior to monkey patch it's :* implementation.
class Fixnum # We just re-opened the class
def *(arg) # We're redefining the * message - this destroys the previous implementation!!!
arg * self
end
end
2 * "Words"
# => WordsWords
# However, we've now lost the ability to do normal multiplication.
# Additionally, we'll overflow the stack trying to multiply to
# Fixnums.
2 * 2
# SystemStackError: stack level too deep
# Open classes and monkey patching have their uses. Just be aware
# that with great power comes great responsibility. Always stop to
# ask yourself if this is really a good idea; and try to never change
# existing behavior if you can.
# Level Up!
# So what if we just wanted to extend the existing functionality.
# There are a lot of ways to do this and covering each, along with
# the pros and cons is very out of scope right now. I'm just going
# to show one simple way to illustrate how it could be done.
#
# You'll need a new IRB or Pry session so we get the original
# implementation for Fixnum back.
class Fixnum # We just re-opened the class
# We need to store a reference to the original implementation
# One way to do this is to create an alias for the original
# implementation so we can call it later.
# Be sure to pick a descriptive name so that it isn't
# accidentally overwritten by something else.
alias :star_without_string_support :*
def *(arg) # We're redefining the * message - this destroys the previous implementation!!!
if arg.is_a? String
arg * self
else
star_without_string_support arg
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment