Skip to content

Instantly share code, notes, and snippets.

@JoshCheek
Last active August 29, 2015 14:22
Show Gist options
  • Save JoshCheek/8ea9796b823e8fbbc019 to your computer and use it in GitHub Desktop.
Save JoshCheek/8ea9796b823e8fbbc019 to your computer and use it in GitHub Desktop.
Objjjjj model!

What is this?

This is the notes we made while going through 1505's Object Model at Turing School. Unfortunately, we lost an hour and a whiteboard during Object Model 1, and had to change venues. Fortunately, we got to drink, and I got the first bit recorded.

Quiz!

A while back I made this quiz. I've completely changed the material since I gave this, but the takeaways are still the same.

Video Lecture

I was able to record the first half of our lecture. Ideally we would have had 3 hours, not 2, and ideally the second half would have gotten recorded, but my hard drive filled up :(

Notes

I placed our notes in this repo, we'll keep building on them as we get more Object Models.

Challenges

I added some challenges to this repo. Go through them using Seeing Is Believing like this:

  1. Uncomment the marked lines
  2. Predict what you think the answer will be
  3. Run them with Command+Option+n to verify
  4. If you got the answer wrong, stop and consider why.
  • Did you miss something? Make a point to identify and notice it.
  • Is there something you don't understand? Can you identify the disparity between what you expected happened?

...I don't actually care very much about the Object Model. It's mostly important because you're writing a lot of Ruby right now, and this is the underlying explanation that makes Ruby make sense. The real thing I want you to learn is the scientific method. Because with the scientific method, you can learn anything. Teaching myself the object model was where I learned the scientific method.

  • Observe some behaviour.
  • Hypothesize about what could explain it.
  • Come up with an experiment to see if the explanation is correct.
    • example: "If self is always the object we called the method on, then I could put a method in a superclass, and self will be the instance of the subclass"
    • Run the experiment... I built you a labratory, and I try to illustrate how to use it as often as possible.
    • Is the experiment correct? You are more confident in the hypothesis.
    • Is the experiment incorrect? Go back to the first step :)

No one taught me the object model, I experimented my way into understanding over about 5 years (and then, years later learned C and verified that some of the crazier conclusions I'd reached were, in fact, correct, by reading Ruby's source code), and I continue to do this.

It doesn't matter how beautiful your theory is, it doesn't matter how smart you are. If it doesn't agree with experiment, it's wrong.

Richard P. Feynman

Never believe anything you can't prove, even if I say it to you ;P

# Run these with `command + option + n`, NOT `b`
# ===== Toplevel methods are defined where? =====
def rawr!
"#{self} says: rawr!"
end
public :rawr!
# *****
# What class is rawr! defined in
# method(:rawr!).owner # =>
# *****
# Think of some objects that inherit from this class
# and show you can call it on them
# <your example here>
# ===== What do bindings tell us? =====
# Here we have a method that returns an object wrapping the binding it executed in
# We can evaluate code within the context of that binding to find out about it
def get_binding
a = 123
binding
end
b = get_binding
# *****
# What is self in that binding?
# b.eval 'self' # =>
# *****
# What are its local variables?
# b.eval 'local_variables' # =>
# *****
# What is the value of a?
a = 99
# b.eval 'a' # =>
# *****
# The binding tracks what `self` is, why does this matter?
# What will we see the second time we run this?
b.eval 'instance_variables'
@abc = 123
# b.eval 'instance_variables' # =>
# ===== Calling methods pushes bindings onto the callstack =====
# We can see the callstack with the `caller` method.
def you_rang?
# *****
# How many bindings are on the callstack?
# caller.size # =>
# *****
# Where did we call it from?
# caller # =>
end
you_rang?
# *****
# What will we see, before and after the calls of each of these lines below?
def call1
caller.size
call2
caller.size
end
def call2
caller.size
call3
caller.size
end
def call3
caller.size
"zomg".call4
caller.size
end
class String
def call4
caller.size
end
end
caller.size
call1
caller.size
# ===== The last line of a method is returned to the caller =====
# *****
# What will we see returned from call1?
def call1
call2
call3
end
def call2
222
end
def call3
call4
333
end
def call4
444
end
call1
# ===== Arguments are evaluated first =====
# Each of the expressions below will evaluate its argument first,
# then it will evaluate itself. What will we see on eac line?
def call1(n)
# n # =>
1 + n
end
def call2(n)
# n # =>
n + 2
end
def call3(n)
# n # =>
n + 3
end
# call3(
# call2(
# call1(0) # =>
# ) # =>
# ) # =>
# ===== Instance Variables =====
# An instance is a collection of instance variables with a pointer to its class,
# it is like the base of a linked list, pointing at the first node in the list
# (typically named "head")
# What will we see returned from 159?
class Fruit
def initialize(banana)
@apple = banana
@pear = "#{banana} boat"
end
def pear
@apple
end
end
fruit = Fruit.new('orange')
# fruit.pear # =>
# ===== Attr Whatevers =====
# The attr_accessor (et all) define define methods that get/set the instance variables of the same name
class Fruit
attr_accessor :apple
def initialize(banana)
@apple = banana
self.apple = "#{banana} boat"
end
def pear
@apple
end
end
#*****
# What will we see on these two lines?
fruit = Fruit.new('orange')
# fruit.pear # =>
# fruit.apple # =>
# *****
# We can punch the object in the face and rearrange its guts with metaprogramming
# Here, I go into it and set @apple = 'pineapple'
# What will we see in the following expressions?
fruit.instance_variable_set '@apple', 'pineapple'
# fruit.pear # =>
# fruit.apple # =>
fruit.apple = 'mango'
# fruit.pear # =>
# fruit.apple # =>
# ===== Classes are a linked list called inheritance =====
# Get classy, stay super
class A
def zomg
'a'
end
end
class B < A
def zomg
'b'
end
end
class C < B
end
class D < A
end
# *****
# What will we see on each of these lines?
# A.new.zomg # =>
# B.new.zomg # =>
# C.new.zomg # =>
# D.new.zomg # =>
# ===== We can use super to access the definition in the superclass chain =====
class C1
def m
'1'
end
end
class C2 < C1
def m
super + '2'
end
end
class C3 < C2
def m
super + '3'
end
end
# *****
# What will we see on each of these lines?
# C1.new.m # =>
# C2.new.m # =>
# C3.new.m # =>
# ===== Once again, but with malice =====
class W
def zomg() '1' + wtf end
def wtf() '2' end
def bbq() '3' end
end
class X < W
def zomg() super end
def wtf() '4' + bbq end
def bbq() super end
end
class Y < X
def zomg() '6' + super end
def wtf() '7' + super end
def bbq() '8' + super end
end
#*****
# W.new.zomg # =>
# X.new.zomg # =>
# Y.new.zomg # =>
# ===== Chaining method calls =====
# When we call a method, we call it on whatever the expression evaluates to
# This means that chaining methods leads to methods called on the return value of the previous expression
#*****
# What will this expression evaluate to?
# 'abc'.upcase.reverse.downcase.chars.first # =>
#*****
# It doesn't matter if you split the expression across lines, what will we see?
# 'abc' # =>
# .upcase # =>
# .reverse # =>
# .downcase # =>
# .chars # =>
# .first # =>
#*****
# The dot can go on the preceeding line, or the current line
# 'abc'. # =>
# upcase. # =>
# reverse. # =>
# downcase. # =>
# chars. # =>
# first # =>
#*****
# We can get all funky with the dot (best practices, ya know?)
# 'abc'. # =>
# upcase .reverse # =>
# .downcase. # =>
# chars # =>
# . first # =>
# ===== Mixing and matching args and chaining =====
#*****
# Uncomment each of the following lines, what will we see?
def z(a)
# a + a # =>
end
def w(a)
# a # =>
# .reverse # =>
end
# (w (z 'abc').upcase).chars # =>
class JoshBinding
def initialize(local_vars, slf, retrurn_value)
@local_vars = local_vars
@slf = slf
@return_value = return_value
end
end
# Stack
# holds my bindings
# when I call a method
# it creates a binding
# puts it on the top of the stack
# This thing will show you the stack:
# def caller_back
# caller.map { |line| line[/:\d+:/][1...-1].to_i }.reverse
# end
class JoshClass
def initialize(methods, superclass, ivars, klass)
@methods = methods
@superclass = superclass
@ivars = ivars
@klass = klass
end
end
class JoshObject # aka JoshInstance
def initialize(ivars, klass)
@ivars = ivars
@klass = klass
end
end
# When I want to call a method on an object
# I start at it's class
# Until I find the method, I look in the class's superclass
# If I don't find it, I explode
# If I do find it
# I make a new binding
# I set self to the object that I called the method on
# I set the local variables to the names from the parameters
# and their values will be whatever arguments I pass
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment