Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Module Magic

jeg2
August 28, 2009

Module Magic

A deep dive into how Ruby's modules really fit into the language.

jeg2

August 28, 2009
Tweet

More Decks by jeg2

Other Decks in Technology

Transcript

  1. JAMES EDWARD GRAY II Created the Ruby Quiz and wrote

    that book Built FasterCSV (now CSV), HighLine (with Greg), Elif, and some other scarier experiments
  2. JAMES EDWARD GRAY II Created the Ruby Quiz and wrote

    that book Built FasterCSV (now CSV), HighLine (with Greg), Elif, and some other scarier experiments Documented some of Ruby
  3. JAMES EDWARD GRAY II Created the Ruby Quiz and wrote

    that book Built FasterCSV (now CSV), HighLine (with Greg), Elif, and some other scarier experiments Documented some of Ruby http://blog.grayproductions.net/
  4. JAMES EDWARD GRAY II Created the Ruby Quiz and wrote

    that book Built FasterCSV (now CSV), HighLine (with Greg), Elif, and some other scarier experiments Documented some of Ruby http://blog.grayproductions.net/ http://twitter.com/JEG2
  5. RUBYKAIGI2009 See how the Japanese do conferences The translation time

    allows you to think more Meet nice Rubyists from Japan and other places
  6. RUBYKAIGI2009 See how the Japanese do conferences The translation time

    allows you to think more Meet nice Rubyists from Japan and other places See Japan!
  7. A TRIVIAL MIXIN I’m sure most of us know this

    module Mixin def shared_method puts "Called!" end end ! class Whatever include Mixin end ! Whatever.new.shared_method
  8. A TRIVIAL MIXIN I’m sure most of us know this

    module Mixin def shared_method puts "Called!" end end ! class Whatever include Mixin end ! Whatever.new.shared_method Called!
  9. class A def call puts "A" end end ! %w[B

    C D].each do |name| eval <<-END_RUBY module #{name} def call puts "#{name}" super end end END_RUBY end ! class E < A include B include C include D def call puts "E" super end end ! E.new.call
  10. E D C B A class A def call puts

    "A" end end ! %w[B C D].each do |name| eval <<-END_RUBY module #{name} def call puts "#{name}" super end end END_RUBY end ! class E < A include B include C include D def call puts "E" super end end ! E.new.call
  11. class A def call puts "A" end end ! %w[B

    C D].each do |name| eval <<-END_RUBY module #{name} def call puts "#{name}" super end end END_RUBY end ! a = A.new a.extend(B) a.extend(C) a.extend(D) a.call
  12. class A def call puts "A" end end ! %w[B

    C D].each do |name| eval <<-END_RUBY module #{name} def call puts "#{name}" super end end END_RUBY end ! a = A.new a.extend(B) a.extend(C) a.extend(D) a.call D C B A
  13. INVISIBLE CLASS class << a; p ancestors end [D, C,

    B, A, Object, Kernel, BasicObject]
  14. INVISIBLE CLASS class << a; p ancestors end [D, C,

    B, A, Object, Kernel, BasicObject] The (invisible) “singleton class” is here
  15. class A def call puts "A" end end ! %w[B

    C D].each do |name| eval <<-END_RUBY module #{name} def call puts "#{name}" super end end END_RUBY end ! a = A.new a.extend(B) a.extend(C) a.extend(D) class << a def call puts "Invisible" super end end a.call
  16. class A def call puts "A" end end ! %w[B

    C D].each do |name| eval <<-END_RUBY module #{name} def call puts "#{name}" super end end END_RUBY end ! a = A.new a.extend(B) a.extend(C) a.extend(D) class << a def call puts "Invisible" super end end a.call Invisible D C B A
  17. JUST A SHORTCUT We now know what extend() really is

    obj.extend(Mod) class << obj include Mod end
  18. GROUP CONSTANTS Group constants/classes and even mix them in class

    Logger # ... # Logging severity. module Severity DEBUG = 0 INFO = 1 WARN = 2 ERROR = 3 FATAL = 4 UNKNOWN = 5 end include Severity # ... end
  19. GROUP CONSTANTS Group constants/classes and even mix them in class

    Logger # ... # Logging severity. module Severity DEBUG = 0 INFO = 1 WARN = 2 ERROR = 3 FATAL = 4 UNKNOWN = 5 end include Severity # ... end module JSON class Array # ... end class Object # ... end # ... end
  20. DUAL INTERFACE Some modules are both a namespace and a

    mixin module MoreMath def self.dist(x1, y1, x2, y2) Math.sqrt( (x2 - x1) ** 2 + (y2 - y1) ** 2 ) end end
  21. DUAL INTERFACE Some modules are both a namespace and a

    mixin module MoreMath def self.dist(x1, y1, x2, y2) Math.sqrt( (x2 - x1) ** 2 + (y2 - y1) ** 2 ) end end module MoreMath extend Math def self.dist( x1, y1, x2, y2 ) sqrt( (x2 - x1) ** 2 + (y2 - y1) ** 2 ) end end
  22. MIXIN YOURSELF Better than Ruby’s module_function() module MiniLogger extend self

    def logger $stdout end def log(message) logger.puts "%s: %s" % [ Time.now.strftime("%D %H:%M:%S"), message ] end end ! if __FILE__ == $PROGRAM_NAME MiniLogger.log "Called as a module method and " + "written to $stdout." end
  23. MIXIN YOURSELF Better than Ruby’s module_function() require "mini_logger" ! class

    Whatever include MiniLogger def logger @logger ||= open("whatever.log", "w") end def initialize log "Called as an " + "instance method " + "and written to " + "a file." end end ! Whatever.new module MiniLogger extend self def logger $stdout end def log(message) logger.puts "%s: %s" % [ Time.now.strftime("%D %H:%M:%S"), message ] end end ! if __FILE__ == $PROGRAM_NAME MiniLogger.log "Called as a module method and " + "written to $stdout." end
  24. LIMITED MAGIC Summon new error types as needed module Errors

    class BaseError < RuntimeError; end def self.const_missing(error_name) if error_name.to_s =~ /\wError\z/ const_set(error_name, Class.new(BaseError)) else super end end end ! p Errors::SaveError
  25. A BIT TOO CLEVER If RDoc can’t read it, it’s

    probably too clever require "ostruct" ! class << Config = OpenStruct.new def update_from_config_file(path = config_file) eval <<-END_UPDATE config = self #{File.read(path)} config END_UPDATE end # ... end ! Config.config_file = "config.rb" Config.update_from_config_file
  26. A BIT TOO CLEVER If RDoc can’t read it, it’s

    probably too clever require "ostruct" ! class << Config = OpenStruct.new def update_from_config_file(path = config_file) eval <<-END_UPDATE config = self #{File.read(path)} config END_UPDATE end # ... end ! Config.config_file = "config.rb" Config.update_from_config_file config.command = "ls" config.retries = 42 # ...
  27. LESS MAGIC RDoc and I can both read it now

    require "ostruct" ! # These extra methods are mixed into the OpenStruct stored in Config. module Configured # This method loads configuration settings from a plain Ruby file. def update_from_config_file(path = config_file) eval <<-END_UPDATE config = self #{File.read(path)} config END_UPDATE end # ... end # This constant holds all global configuration, see Configured for details. Config = OpenStruct.new.extend(Configured)
  28. WITH CLASS METHODS A classic pattern made popular by Rails

    module DoubleMixin module ClassMethods # ... end module InstanceMethods # ... end def self.included(receiver) receiver.extend(ClassMethods) receiver.send(:include, InstanceMethods) end end
  29. LABELING OBJECTS You can use a do-nothing module as a

    type module DRb # ... module DRbUndumped def _dump(dummy) # :nodoc: raise TypeError, 'can\'t dump' end end # ... class DRbMessage # ... def dump(obj, error=false) # :nodoc: obj = make_proxy(obj, error) if obj.kind_of? DRbUndumped # ... end # ... end # ... end
  30. OBJECT EDITING Using hooks to edit objects p "ruby".strip!. capitalize!

    ! # NoMethodError: # undefined method # `capitalize!' for # nil:NilClass
  31. OBJECT EDITING Using hooks to edit objects module SafelyChainable def

    self.extended(singleton) singleton.methods.grep(/\w!\z/). each do |bang| singleton.instance_eval <<-END_RUBY def #{bang} super self end END_RUBY end end end ! p "ruby".extend(SafelyChainable). strip!.capitalize! p "ruby".strip!. capitalize! ! # NoMethodError: # undefined method # `capitalize!' for # nil:NilClass
  32. SUMMARY Master Ruby’s method lookup; it’s worth the effort Modules

    are a terrific at limiting the scope of magic
  33. SUMMARY Master Ruby’s method lookup; it’s worth the effort Modules

    are a terrific at limiting the scope of magic Remember, modules can modify individual objects
  34. SUMMARY Master Ruby’s method lookup; it’s worth the effort Modules

    are a terrific at limiting the scope of magic Remember, modules can modify individual objects Try replacing some inheritance with extend()