- are nodes in the stack
- store local_variables
- have a return value
- have a self
- are a node in a linked list called "inheritance"
- they store methods
- Has a link to the head of the list
- The "class" is the head of the list
- Store @instance_variables
This code is somewhere out there in Ruby
class Class
def new(*args)
instance = allocate()
instance.__send__ :initialize, *args
instance
end
end
Here is our code:
class User
def initialize(first_name, last_name)
@first_name = first_name
@last_name = last_name
end
def full_name
"#{first_name} #{last_name}"
end
def first_name
@first_name
end
def last_name
@last_name
end
end
user = User.new('Bozo', 'Clown')
user.full_name # => "Bozo Clown"
What happens?
- Start with one binding on the stack, its self is pointing at an object called "main"
- We hit
class User
- Create a new class named
User
, it has no methods - push a binding to the stack, with
self
pointing atUser
- Create a new class named
- We hit
def initialize
- Add
initialize
toUser
's methods
- Add
- We hit
def full_name
- Add
full_name
toUser
's methods
- Add
- We hit
def first_name
- Add
first_name
toUser
's methods
- Add
- We hit
def last_name
- Add
last_name
toUser
's methods
- Add
- We hit the
end
of theUser
class- Pop the second binding off the stack, we're back in main
- We hit
User.new('Bozo', 'Clown')
- Create the string
'Bozo'
, an object whose class isString
- Create the string
'Clown'
, an object whose class isString
- Invoke
new
onUser
- Go searching for it at the class pointer
User
's class isClass
, and we find the method there- Push a binding onto the stack, with self set to
User
, the class - Add a local variable
args
which is technically pointing at an array containing'Bozo'
and'Clown'
- We'll magic
allocate
away, what it does is gives us a new object- That object's class is our
self
, which isUser
- That object's class is our
- Store the object in a local variable
instance
, in the current binding - We hit
instance.initialize(*args)
- Look up the local variable
instance
, and the local variableargs
- Find the method:
- get classy on
instance
to locateUser
- Add a binding to the stack with self set to the user
instance
was pointing at - Add the local variable
first_name
to the third binding, and point at'Bozo'
, the first value in the args - Add the local variable
last_name
to the third binding, and point at'Clown'
, the last value in the args - We hit
@first_name = first_name
- From our binding, look up
first_name
to find'Bozo'
- From our binding, look up
self
to find our user - Add the instance variable
@first_name
to our user, it points at'Bozo'
, the value it got from the local variable.
- From our binding, look up
- We hit
@last_name = last_name
- From our binding, look up
last_name
to find'Clown'
- From our binding, look up
self
to find our user - Add the instance variable
@last_name
to our user, it points at'Clown'
.
- From our binding, look up
- We leave the
initialize
method- Pop the third binding off the stack (erase it)
- We are now back in the second binding, in the
new
method
- get classy on
- We hit
instance
and look up the local variable to find our user
- We leave the
new
method- Pop the second binding off the stack (erase it)
- We return to the first binding, at
user = User.new('Bozo', 'Clown')
- We now set the user that was returned into a local variable
user
in the first binding
- Create the string
- We hit
user.full_name
- Look up
user
to find our instance - Call
full_name
- From our user, get classy to find the
User
class - We find its
full_name
method here - Add a binding onto the stack, with self pointing at our user (because that's the object we called the method on)
- We hit "#{first_name} #{last_name}"
first_name
is not a local, so it must be a method, call it:- From the binding, find
self
, then get classy, and we find the method - Add a binding to the stack with
self
set to the second binding'sself
(the user) - We hit
@first_name
- From the binding, follow
self
to find the user - Look up its
@first_name
instance variable to find'Bozo'
- From the binding, follow
- We return the to the caller
- pop the binding off the stack
- From the binding, find
last_name
is not a local, so it must be a method, call it:- From the binding, find
self
, then get classy, and we find the method - Add a binding to the stack with
self
set to the second binding'sself
(the user) - We hit
@flast_name
- From the binding, follow
self
to find the user - Look up its
@last_name
instance variable to find'Clown'
- From the binding, follow
- We return the to the caller
- pop the binding off the stack
- We now put
'Bozo'
and'Clown'
into a new string- Create a new object for "Bozo Clown", its class is String
- From the binding, find
- We leave the
full_name
method- Pop the second binding off the stack
- Return "Bozo Clown" to the
user.full_name
- From our user, get classy to find the
- Look up
- Our program ends
Modules are bags of methods that can be included in another class's inheritance hierarchy. When you include the module, Ruby creates an invisible class whose method table is pointing at the module's method table, and inserts it at the superclass.
Here is the code example we made up in class:
module Talkative
def speak
"I said: #{@sound}"
end
end
module Communicative
def greet
"Hi, my name is #{@name}"
end
end
class Firetruck
Firetruck.ancestors # => [Firetruck, Object, Kernel, BasicObject]
include Talkative
Firetruck.ancestors # => [Firetruck, Talkative, Object, Kernel, BasicObject]
def initialize(sound)
@sound = sound
end
end
class Animal
include Communicative
include Talkative
def initialize(name, sound)
@name = name
@sound = sound
end
end
class Person
include Communicative
def initialize(name)
@name = name
end
end
Person.new("Bill").greet # => "Hi, my name is Bill"
Animal.new("Rusty", "woof").greet # => "Hi, my name is Rusty"
Animal.new("Rusty", "woof").speak # => "I said: woof"
Firetruck.new("wroooowoooo").speak # => "I said: wroooowoooo"
There is an attached picture of the class structure it creates.
Here are some challenges to exercise this knowledge.
# 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 # =>
# ===== Modules =====
# When you include a module, it makes a class and inserts it into the hierarchy
class A
def wat
'a'
end
end
module WatInTheWorld
def wat
'!?' + super
end
end
class B < A
include WatInTheWorld
def wat
'b' + super
end
end
#*****
# What will we see here?
# B.new.wat # =>
# ===== Silence!! DEstroy him!! =====
# Dew! Bew! Dew-dew-dew! Bew!
module InSpace
attr_reader :current_status
def initialize(current_status, *whatevz)
@current_status = current_status
super(*whatevz)
end
end
class Human
attr_reader :name
def initialize(name)
@name = name
end
end
class Student < Human
include InSpace
attr_reader :lesson
def initialize(lesson, *o_O)
@lesson = lesson
super *o_O
end
end
students_in_space = Student.new(
"The future is quite different to the present",
"Though one thing we have in common with the present is we still call it the present, even though its the future",
"What you call 'the present', we call 'the past', so... you guys are way behind"
)
#*****
# We used poisonous gasses, (with traces of lead)
# and we poisoned their asses (actually, their lungs)
# students_in_space.current_status # =>
# students_in_space.name # =>
# students_in_space.lesson # =>
We can define a method on some particlar object
a = "a"
def a.x
"I am: #{self.inspect}"
end
a # => "a"
a.x # => "I am: \"a\""
'a'.x rescue $! # => #<NoMethodError: undefined method `x' for "a":String>
This places it on the object's singleton class
a.singleton_class.instance_methods(false) # => [:x]
The singleton class is a class specifically for that object, The object's class pointer points at it, and its superclass points to the original class So the object can call those methods, and all the old ones, but no other object can call its methods.
a.singleton_class # => #<Class:#<String:0x007fc9cd06a9c0>>
.superclass # => String
The most common way you'll see this done defining methods on self
within a class.
class String
def self.empty
''
end
end
String.empty # => ""
If we include a module into the singleton class, that will bet placed between it and the original class
Here, we have to use ancestors, because superclass
lies.
module M
def zomg
'zomg!'
end
end
a = "a"
a.singleton_class.class_eval { include M }
a.singleton_class # => #<Class:#<String:0x007fc9cd0699d0>>
.ancestors.take(3) # => [#<Class:#<String:0x007fc9cd0699d0>>, M, String]
A common way to open the singleton class is with class << ...
a.singleton_class # => #<Class:#<String:0x007fc9cd0699d0>>
class << a
self # => #<Class:#<String:0x007fc9cd0699d0>>
include M
end
This pattern is common enough that there is a method to do it for you: extend
.
a = "a"
a.zomg rescue $! # => #<NoMethodError: undefined method `zomg' for "a":String>
a.extend M
a.zomg # => "zomg!"
Not something you should do in your code, but we can test our understanding of the object model by anticipating what each of these methods will return.
a = 'a'
a.singleton_class # => #<Class:#<String:0x007fdac19dc738>>
.singleton_class # => #<Class:#<Class:#<String:0x007fdac19dc738>>>
.singleton_class # => #<Class:#<Class:#<Class:#<String:0x007fdac19dc738>>>>
.singleton_class # => #<Class:#<Class:#<Class:#<Class:#<String:0x007fdac19dc738>>>>>
.class # => Class
.superclass # => Module
.superclass # => Object
.class # => Class
.new # => #<Class:0x007fdac19d7878>
.new # => #<#<Class:0x007fdac19d7878>:0x007fdac19d75f8>
.class # => #<Class:0x007fdac19d7878>
.class # => Class