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. MODULE MAGIC
    and my trip to RubyKaigi2009

    View Slide

  2. JAMES EDWARD
    GRAY II

    View Slide

  3. JAMES EDWARD
    GRAY II
    Created the Ruby Quiz and wrote that book

    View Slide

  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

    View Slide

  5. 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

    View Slide

  6. 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/

    View Slide

  7. 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

    View Slide

  8. LSRC SPEECHES
    TV shows geeks should know!

    View Slide

  9. LSRC SPEECHES
    TV shows geeks should know!

    View Slide

  10. RUBYKAIGI2009

    View Slide

  11. RUBYKAIGI2009

    View Slide

  12. RUBYKAIGI2009
    See how the Japanese do
    conferences

    View Slide

  13. RUBYKAIGI2009
    See how the Japanese do
    conferences
    The translation time
    allows you to think more

    View Slide

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

    View Slide

  15. 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!

    View Slide

  16. View Slide

  17. View Slide

  18. View Slide

  19. View Slide

  20. View Slide

  21. View Slide

  22. View Slide

  23. View Slide

  24. View Slide

  25. View Slide

  26. View Slide

  27. View Slide

  28. MIXIN MODULES

    View Slide

  29. A TRIVIAL MIXIN
    I’m sure most of us know this

    View Slide

  30. 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

    View Slide

  31. 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!

    View Slide

  32. View Slide

  33. View Slide

  34. View Slide

  35. View Slide

  36. View Slide

  37. 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

    View Slide

  38. 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

    View Slide

  39. INHERITANCE
    p E.ancestors

    View Slide

  40. INHERITANCE
    p E.ancestors
    [E, D, C, B, A, Object, Kernel, BasicObject]

    View Slide

  41. View Slide

  42. View Slide

  43. View Slide

  44. View Slide

  45. View Slide

  46. 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

    View Slide

  47. 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

    View Slide

  48. INVISIBLE CLASS
    class << a; p ancestors end

    View Slide

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

    View Slide

  50. INVISIBLE CLASS
    class << a; p ancestors end
    [D, C, B, A, Object, Kernel, BasicObject]
    The (invisible) “singleton class” is here

    View Slide

  51. View Slide

  52. 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

    View Slide

  53. 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

    View Slide

  54. JUST A SHORTCUT
    We now know what extend() really is

    View Slide

  55. JUST A SHORTCUT
    We now know what extend() really is
    obj.extend(Mod)

    View Slide

  56. JUST A SHORTCUT
    We now know what extend() really is
    obj.extend(Mod)
    class << obj
    include Mod
    end

    View Slide

  57. NAMESPACE
    MODULES

    View Slide

  58. GROUP CONSTANTS
    Group constants/classes and even mix them in

    View Slide

  59. 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

    View Slide

  60. 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

    View Slide

  61. View Slide

  62. View Slide

  63. View Slide

  64. View Slide

  65. DUAL INTERFACE
    Some modules are both a namespace and a mixin

    View Slide

  66. 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

    View Slide

  67. 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

    View Slide

  68. MIXIN YOURSELF
    Better than Ruby’s module_function()

    View Slide

  69. 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

    View Slide

  70. 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

    View Slide

  71. View Slide

  72. View Slide

  73. View Slide

  74. View Slide

  75. LIMITED MAGIC
    Summon new error types as needed

    View Slide

  76. 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

    View Slide

  77. SOME EXAMPLES
    Bridging the theory to implementation gap

    View Slide

  78. SOME EXAMPLES
    Bridging the theory to implementation gap

    View Slide

  79. A BIT TOO CLEVER
    If RDoc can’t read it, it’s probably too clever

    View Slide

  80. 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

    View Slide

  81. 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
    # ...

    View Slide

  82. LESS MAGIC
    RDoc and I can both read it now

    View Slide

  83. 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)

    View Slide

  84. View Slide

  85. View Slide

  86. View Slide

  87. View Slide

  88. WITH CLASS METHODS
    A classic pattern made popular by Rails

    View Slide

  89. 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

    View Slide

  90. View Slide

  91. View Slide

  92. View Slide

  93. View Slide

  94. LABELING OBJECTS
    You can use a do-nothing module as a type

    View Slide

  95. 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

    View Slide

  96. OBJECT EDITING
    Using hooks to edit objects

    View Slide

  97. OBJECT EDITING
    Using hooks to edit objects
    p "ruby".strip!.
    capitalize!
    !
    # NoMethodError:
    # undefined method
    # `capitalize!' for
    # nil:NilClass

    View Slide

  98. 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

    View Slide

  99. SUMMARY

    View Slide

  100. SUMMARY

    View Slide

  101. SUMMARY
    Master Ruby’s method
    lookup; it’s worth the effort

    View Slide

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

    View Slide

  103. 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

    View Slide

  104. 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()

    View Slide

  105. QUESTIONS?

    View Slide