Delayed evaluation of append_features

Discussion in 'Ruby' started by Daniel Berger, Jan 19, 2010.

  1. Hi,

    I was just reading this bit:

    http://judofyr.net/posts/never-gonna-let-you-go.html

    And that had me wondering about my "interface" implementation. At the
    moment, users must declare the methods before they implement the
    interface. However, now I'm wondering if I could somehow delay the
    evaluation of append_features until *after* methods have been
    declared.

    Could continuations be used for this? Below is the code and a sample
    at the end.

    # A module for implementing Java style interfaces in Ruby. For more
    information
    # about Java interfaces, please see:
    #
    # http://java.sun.com/docs/books/tutorial/java/concepts/interface.html
    #
    module Interface
    # The version of the interface library.
    Interface::VERSION = '1.1.0a'

    # Raised if a class or instance does not meet the interface
    requirements.
    class MethodMissing < RuntimeError; end

    alias :extends :extend

    private

    def extend_object(obj)
    return append_features(obj) if Interface === obj
    append_features(class << obj; self end)
    included(obj)
    end

    def append_features(mod)
    return super if Interface === mod

    # Is this a sub-interface?
    inherited = (self.ancestors-[self]).select{ |x| Interface === x }
    inherited = inherited.map{ |x| x.instance_variable_get('@ids') }

    # Store required method ids
    ids = @ids + inherited.flatten
    @unreq ||= []

    # Iterate over the methods, minus the unrequired methods, and
    raise
    # an error if the method has not been defined.
    (ids - @unreq).uniq.each do |id|
    id = id.to_s if RUBY_VERSION.to_f < 1.9
    unless mod.instance_methods(true).include?(id)
    raise Interface::MethodMissing, id
    end
    end

    super mod
    end

    public

    # Accepts an array of method names that define the interface. When
    this
    # module is included/implemented, those method names must have
    already been
    # defined.
    #
    def required_methods(*ids)
    @ids = ids
    end

    alias requires required_methods

    # Accepts an array of method names that are removed as a requirement
    for
    # implementation. Presumably you would use this in a sub-interface
    where
    # you only wanted a partial implementation of an existing interface.
    #
    def unrequired_methods(*ids)
    @unreq ||= []
    @unreq += ids
    end
    end

    class Object
    # The interface method creates an interface module which typically
    sets
    # a list of methods that must be defined in the including class or
    module.
    # If the methods are not defined, an Interface::MethodMissing error
    is raised.
    #
    # A interface can extend an existing interface as well. These are
    called
    # sub-interfaces, and they can included the rules for their parent
    interface
    # by simply extending it.
    #
    # Example:
    #
    # # Require 'alpha' and 'beta' methods
    # AlphaInterface = interface{
    # required_methods :alpha, :beta
    # }
    #
    # # A sub-interface that requires 'beta' and 'gamma' only
    # GammaInterface = interface{
    # extends AlphaInterface
    # required_methods :gamma
    # unrequired_methods :alpha
    # }
    #
    # # Raises an Interface::MethodMissing error because :beta is not
    defined.
    # class MyClass
    # def alpha
    # # ...
    # end
    # implements AlphaInterface
    # end
    #
    def interface(&block)
    mod = Module.new
    mod.extend(Interface)
    mod.instance_eval(&block)
    mod
    end
    end

    class Module
    alias :implements :include
    end

    FooInterface = interface{
    requires :alpha, :beta
    }

    # How it works now
    class Foo
    def alpha;end
    def beta;end
    implements FooInterface
    end

    # How I want it to work
    class Foo
    implements FooInterface
    def alpha;end
    def beta;end
    end

    Regards,

    Dan
     
    Daniel Berger, Jan 19, 2010
    #1
    1. Advertisements

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments (here). After that, you can post your question and our members will help you out.