$ irb // Array is a "class". It holds the hash table of all // methods common to all Array instances (like "a" below), // and links to other classes, which contains more // methods that can be called on an Array. This is // what "inheriting" a class means in Ruby: if a method // in not in Array's own table of methods, it is perhaps // in Array's "ancestors", i.e., classes Array links // to. Method lookup goes up that chain, as explained in // https://blog.jcoglan.com/2013/05/08/how-ruby-method-dispatch-works/ // I added a bunch of examples here to what we did in class, so // read carefully. irb(main):001:0> Array.ancestors => [Array, Enumerable, Object, Kernel, BasicObject] // a is an "instance" (in OO parlance) of Array: irb(main):002:0> a = Array.new => [] irb(main):003:0> a.class => Array // Array itself is an instance of Class: irb(main):004:0> Array.class => Class // And Class loops upon itself: irb(main):005:0> Class.class => Class // You can ask Array for all methods that its instances will have in common. // This tells you that calling any of these on "a" will not result in // "huh? don't know what to do" error. // To get this, instance_methods walks the chain of ancestors and merges the // their specific method tables into one list: irb(main):006:0> Array.instance_methods => [:inspect, :to_s, :to_a, :to_ary, :frozen?, :==, :eql?, :hash, :[], :[]=, :at, :fetch, :first, :last, :concat, :<<, :push, :pop, :shift, :unshift, :insert, :each, :each_index, :reverse_each, :length, :size, :empty?, :find_index, :index, :rindex, :join, :reverse, :reverse!, :rotate, :rotate!, :sort, :sort!, :sort_by!, :collect, :collect!, :map, :map!, :select, :select!, :keep_if, :values_at, :delete, :delete_at, :delete_if, :reject, :reject!, :zip, :transpose, :replace, :clear, :fill, :include?, :<=>, :slice, :slice!, :assoc, :rassoc, :+, :*, :-, :&, :|, :uniq, :uniq!, :compact, :compact!, :flatten, :flatten!, :count, :shuffle!, :shuffle, :sample, :cycle, :permutation, :combination, :repeated_permutation, :repeated_combination, :product, :take, :take_while, :drop, :drop_while, :bsearch, :pack, :entries, :sort_by, :grep, :find, :detect, :find_all, :flat_map, :collect_concat, :inject, :reduce, :partition, :group_by, :all?, :any?, :one?, :none?, :min, :max, :minmax, :min_by, :max_by, :minmax_by, :member?, :each_with_index, :each_entry, :each_slice, :each_cons, :each_with_object, :chunk, :slice_before, :lazy, :nil?, :===, :=~, :!~, :class, :singleton_class, :clone, :dup, :taint, :tainted?, :untaint, :untrust, :untrusted?, :trust, :freeze, :methods, :singleton_methods, :protected_methods, :private_methods, :public_methods, :instance_variables, :instance_variable_get, :instance_variable_set, :instance_variable_defined?, :remove_instance_variable, :instance_of?, :kind_of?, :is_a?, :tap, :send, :public_send, :respond_to?, :extend, :display, :method, :public_method, :define_singleton_method, :object_id, :to_enum, :enum_for, :equal?, :!, :!=, :instance_eval, :instance_exec, :__send__, :__id__] // That was asking the Array class. You can also ask any particular instance for the methods // it supports. This list may differ if you add special per-instance methods for just "a" // (see "singleton class" explanations below). irb(main):007:0> a.methods => [:inspect, :to_s, :to_a, :to_ary, :frozen?, :==, :eql?, :hash, :[], :[]=, :at, :fetch, :first, :last, :concat, :<<, :push, :pop, :shift, :unshift, :insert, :each, :each_index, :reverse_each, :length, :size, :empty?, :find_index, :index, :rindex, :join, :reverse, :reverse!, :rotate, :rotate!, :sort, :sort!, :sort_by!, :collect, :collect!, :map, :map!, :select, :select!, :keep_if, :values_at, :delete, :delete_at, :delete_if, :reject, :reject!, :zip, :transpose, :replace, :clear, :fill, :include?, :<=>, :slice, :slice!, :assoc, :rassoc, :+, :*, :-, :&, :|, :uniq, :uniq!, :compact, :compact!, :flatten, :flatten!, :count, :shuffle!, :shuffle, :sample, :cycle, :permutation, :combination, :repeated_permutation, :repeated_combination, :product, :take, :take_while, :drop, :drop_while, :bsearch, :pack, :entries, :sort_by, :grep, :find, :detect, :find_all, :flat_map, :collect_concat, :inject, :reduce, :partition, :group_by, :all?, :any?, :one?, :none?, :min, :max, :minmax, :min_by, :max_by, :minmax_by, :member?, :each_with_index, :each_entry, :each_slice, :each_cons, :each_with_object, :chunk, :slice_before, :lazy, :nil?, :===, :=~, :!~, :class, :singleton_class, :clone, :dup, :taint, :tainted?, :untaint, :untrust, :untrusted?, :trust, :freeze, :methods, :singleton_methods, :protected_methods, :private_methods, :public_methods, :instance_variables, :instance_variable_get, :instance_variable_set, :instance_variable_defined?, :remove_instance_variable, :instance_of?, :kind_of?, :is_a?, :tap, :send, :public_send, :respond_to?, :extend, :display, :method, :public_method, :define_singleton_method, :object_id, :to_enum, :enum_for, :equal?, :!, :!=, :instance_eval, :instance_exec, :__send__, :__id__] // For now these lists are the same ("-" on lists gives you a set difference, // e.g., [1, 2, 3] - [1, 2] and [1, 2, 3, 1] - [1, 2] and [1, 2, 3, 1] - [1, 2, 2] all evaluate to [3]): irb(main):008:0> Array.instance_methods - a.methods => [] // You can check which class up the ancestors chain actually gives you a method. // The method "method" performs the look-up and tells you. // For example, Array's "new" is from Class: irb(main):009:0> Array.method :new => # // So what methods are supplied by Class specifically, not by its ancestors? // Let's see: irb(main):010:0> Class.ancestors => [Class, Module, Object, Kernel, BasicObject] irb(main):011:0> Class.instance_methods - Module.instance_methods - Object.instance_methods - Kernel.instance_methods - BasicObject.instance_methods => [:allocate, :new, :superclass] // Stands to reason. A Class has a method for making new things/objects/instances of that class. // BasicObject is at the end of the chain, and has the most basic methods that // every other kind of thing needs, like basic equality :== (are you the same object // as me?) and :__send__ (if messages cannot be "sent" to an object, it's no good, // what can it do?) irb(main):012:0> BasicObject.instance_methods => [:==, :equal?, :!, :!=, :instance_eval, :instance_exec, :__send__, :__id__] // Note that methods are objects themselves! This is the most powerful idea // called "reflection": you can ask any object about what it can do, and // the result is not just some print-out---it's something you can use, compare, // call (if it's some form of code), etc. // For example, you can compare methods. The following tells you that :== and // :equal? are actually the same method, just aliased for nicer syntax: irb(main):013:0> (BasicObject.method :equal?) == (BasicObject.method :==) => true // Note that you cannot ask "a" for its instance_methods. That's only for // classes. Classes create instances of objects (with :new), objects // don't. Classes stands for "groups of objects/instances/things that allow // the same set of operations"; objects are things/instances of classes. irb(main):014:0> a.instance_methods NoMethodError: undefined method `instance_methods' for []:Array from (irb):13 from /usr/bin/irb:12:in `
' // Of course, you can first ask for the object's class, and then ask that // for all the methods the instances of that class share (through that // class or through its ancestors): irb(main):015:0> a.class.instance_methods => [:inspect, :to_s, :to_a, :to_ary, :frozen?, :==, :eql?, :hash, :[], :[]=, :at, :fetch, :first, :last, :concat, :<<, :push, :pop, :shift, :unshift, :insert, :each, :each_index, :reverse_each, :length, :size, :empty?, :find_index, :index, :rindex, :join, :reverse, :reverse!, :rotate, :rotate!, :sort, :sort!, :sort_by!, :collect, :collect!, :map, :map!, :select, :select!, :keep_if, :values_at, :delete, :delete_at, :delete_if, :reject, :reject!, :zip, :transpose, :replace, :clear, :fill, :include?, :<=>, :slice, :slice!, :assoc, :rassoc, :+, :*, :-, :&, :|, :uniq, :uniq!, :compact, :compact!, :flatten, :flatten!, :count, :shuffle!, :shuffle, :sample, :cycle, :permutation, :combination, :repeated_permutation, :repeated_combination, :product, :take, :take_while, :drop, :drop_while, :bsearch, :pack, :entries, :sort_by, :grep, :find, :detect, :find_all, :flat_map, :collect_concat, :inject, :reduce, :partition, :group_by, :all?, :any?, :one?, :none?, :min, :max, :minmax, :min_by, :max_by, :minmax_by, :member?, :each_with_index, :each_entry, :each_slice, :each_cons, :each_with_object, :chunk, :slice_before, :lazy, :nil?, :===, :=~, :!~, :class, :singleton_class, :clone, :dup, :taint, :tainted?, :untaint, :untrust, :untrusted?, :trust, :freeze, :methods, :singleton_methods, :protected_methods, :private_methods, :public_methods, :instance_variables, :instance_variable_get, :instance_variable_set, :instance_variable_defined?, :remove_instance_variable, :instance_of?, :kind_of?, :is_a?, :tap, :send, :public_send, :respond_to?, :extend, :display, :method, :public_method, :define_singleton_method, :object_id, :to_enum, :enum_for, :equal?, :!, :!=, :instance_eval, :instance_exec, :__send__, :__id__] ////////////////////////// You may skip this at first reading ////////////////////////////////// // There is a twist to this. See how we are calling all kinds of methods on // Array, Class, etc.? How does this work? Well, these "upper-case" objects // are all instances of Class, and this is what really implements the reflection: irb(main):016:0> Array.method :ancestors => # // So, when "ancestors" is called on Array, you first look up the class of Array. // It's Class. Class gets its method "ancestors" from Module. Indeed, Class // point to Module: irb(main):017:0> Class.ancestors => [Class, Module, Object, Kernel, BasicObject] // This structure is self-referential, but it works! A handy diagram is // https://lh4.googleusercontent.com/e_Eml6aYg1udItOLjQCzUKF1L2K1JcjyZTnzYwcP7A=w1530-h800-no // (from http://rubylangfindings.blogspot.com/2013/12/ruby-core-object-model.html) /////////////////////////////////////////////////////////////////////////////////////////////// // So after all this, here is an example of Mixins: // Let's add a method :cdr to Array. First, let's check if that method is there. // We know :max and :min are, but :foo isn't: irb(main):018:0> Array.instance_methods.grep :max => [:max] irb(main):019:0> Array.instance_methods.grep :min => [:min] irb(main):020:0> Array.instance_methods.grep :foo => [] // So, there's no :cdr irb(main):021:0> Array.instance_methods.grep :cdr => [] // It's not in a.methods either: irb(main):022:0> a.methods => [:inspect, :to_s, :to_a, :to_ary, :frozen?, :==, :eql?, :hash, :[], :[]=, :at, :fetch, :first, :last, :concat, :<<, :push, :pop, :shift, :unshift, :insert, :each, :each_index, :reverse_each, :length, :size, :empty?, :find_index, :index, :rindex, :join, :reverse, :reverse!, :rotate, :rotate!, :sort, :sort!, :sort_by!, :collect, :collect!, :map, :map!, :select, :select!, :keep_if, :values_at, :delete, :delete_at, :delete_if, :reject, :reject!, :zip, :transpose, :replace, :clear, :fill, :include?, :<=>, :slice, :slice!, :assoc, :rassoc, :+, :*, :-, :&, :|, :uniq, :uniq!, :compact, :compact!, :flatten, :flatten!, :count, :shuffle!, :shuffle, :sample, :cycle, :permutation, :combination, :repeated_permutation, :repeated_combination, :product, :take, :take_while, :drop, :drop_while, :bsearch, :pack, :entries, :sort_by, :grep, :find, :detect, :find_all, :flat_map, :collect_concat, :inject, :reduce, :partition, :group_by, :all?, :any?, :one?, :none?, :min, :max, :minmax, :min_by, :max_by, :minmax_by, :member?, :each_with_index, :each_entry, :each_slice, :each_cons, :each_with_object, :chunk, :slice_before, :lazy, :nil?, :===, :=~, :!~, :class, :singleton_class, :clone, :dup, :taint, :tainted?, :untaint, :untrust, :untrusted?, :trust, :freeze, :methods, :singleton_methods, :protected_methods, :private_methods, :public_methods, :instance_variables, :instance_variable_get, :instance_variable_set, :instance_variable_defined?, :remove_instance_variable, :instance_of?, :kind_of?, :is_a?, :tap, :send, :public_send, :respond_to?, :extend, :display, :method, :public_method, :define_singleton_method, :object_id, :to_enum, :enum_for, :equal?, :!, :!=, :instance_eval, :instance_exec, :__send__, :__id__] irb(main):023:0> a.methods.grep :cdr => [] // Here I mistyped and quit out of IRB. Let's go back. $ irb // Pasting from add-cdr-to-array.rb: irb(main):001:0> class Array; irb(main):002:1* def cdr irb(main):003:2> self[1..-1] irb(main):004:2> end irb(main):005:1> irb(main):006:1* def null? irb(main):007:2> self.size == 0 irb(main):008:2> end irb(main):009:1> end => nil // And now it's there! irb(main):010:0> Array.instance_methods.grep :cdr => [:cdr] // And it works! irb(main):011:0> [1,2,3].cdr => [2, 3] // and again irb(main):012:0> [1,2,3].cdr.cdr => [3] irb(main):013:0> [1,2,3].cdr.cdr.cdr => [] // and again! irb(main):014:0> [1,2,3].cdr.cdr.cdr.cdr => nil // And then it fails, because nil is an object of a different class, // (NilClass, not Array), that NilClass has no :cdr in its table: irb(main):015:0> [1,2,3].cdr.cdr.cdr.cdr.cdr NoMethodError: undefined method `cdr' for nil:NilClass from (irb):15 from /usr/bin/irb:12:in `
' // Oops, typo. Method names are all lowercase! irb(main):016:0> nil.Class NoMethodError: undefined method `Class' for nil:NilClass from (irb):16 from /usr/bin/irb:12:in `
' // Now I got it right: irb(main):017:0> nil.class => NilClass // ...and no :cdr, of course: irb(main):018:0> nil.methods => [:to_i, :to_f, :to_s, :to_a, :to_h, :inspect, :&, :|, :^, :nil?, :to_r, :rationalize, :to_c, :===, :=~, :!~, :eql?, :hash, :<=>, :class, :singleton_class, :clone, :dup, :taint, :tainted?, :untaint, :untrust, :untrusted?, :trust, :freeze, :frozen?, :methods, :singleton_methods, :protected_methods, :private_methods, :public_methods, :instance_variables, :instance_variable_get, :instance_variable_set, :instance_variable_defined?, :remove_instance_variable, :instance_of?, :kind_of?, :is_a?, :tap, :send, :public_send, :respond_to?, :extend, :display, :method, :public_method, :define_singleton_method, :object_id, :to_enum, :enum_for, :==, :equal?, :!, :!=, :instance_eval, :instance_exec, :__send__, :__id__] // So let's add this method to NilClass! // Oops, typo again! "class" is method (just like "def"), adding a mixin method // is an action, we need a method to do it (which happens to be called "class", // called on the Ruby core). Remember, when a function seems to be called by // itself, like "def", it is actually called on the "self" object that's // currently in scope---remember all of those "putself" opcodes, and the "self" // pointer in the C code of rb_frame_control_t. It's that. irb(main):019:0> Class NilClass ; def cdr ; nil ; end NoMethodError: undefined method `Class' for main:Object from (irb):19 from /usr/bin/irb:12:in `
' // So I didn't notice, and kept piling on wrong syntax: irb(main):020:0> Class NilClass ; def cdr ; nil ; end ; end SyntaxError: (irb):20: syntax error, unexpected keyword_end, expecting end-of-input from /usr/bin/irb:12:in `
' // ... and a few tries later I finally saw it: irb(main):025:0> class NilClass; def cdr ; nil ; end // ...but forgot the end! (notice :1 in 026:1? It's IRB telling me I didn't close // the expression irb(main):026:1> nil.cdr irb(main):027:1> ^D SyntaxError: (irb):26: syntax error, unexpected end-of-input, expecting keyword_end from /usr/bin/irb:12:in `
' // Argh, restart: dhcp-212-243:ruby user$ irb irb(main):001:0> class NilClass; def cdr ; nil ; end irb(main):002:1> end => nil // Ah, finally! "class NilClass; def cdr ; nil ; end ; end" was what I needed, // the first "end" closing "def", the other the block I am passing to "class". irb(main):003:0> nil.cdr => nil // and now it works all the way: irb(main):004:0> class Array; irb(main):005:1* def cdr irb(main):006:2> self[1..-1] irb(main):007:2> end irb(main):008:1> irb(main):009:1* def null? irb(main):010:2> self.size == 0 irb(main):011:2> end irb(main):012:1> end => nil irb(main):013:0> class NilClass; def cdr ; nil ; end ; end => nil irb(main):014:0> [1, 2, 3].cdr.cdr.cdr.cdr.cdr => nil irb(main):015:0> [1, 2, 3].cdr.cdr.cdr.cdr.cdr.cdr.cdr.cdr.cdr => nil // Now add :null? method to Array and NilClass, so that // [1, 2, 3].cdr.cdr.cdr.cdr.null? also works: // (my code skipped here) irb(main):020:0> [1, 2, 3].cdr.cdr.cdr.cdr.null? => true // for [].null? it should arguably be "true": an empty array [] is "null" irb(main):021:0> [1, 2, 3].cdr.cdr.cdr.null? => true irb(main):023:0> [1, 2, 3].cdr.cdr.null? => false