Last active
August 29, 2015 13:56
-
-
Save cupakromer/8851663 to your computer and use it in GitHub Desktop.
On Open Classes and Message Passing in Ruby #retroruby
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"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