Ruby Challenge — цепочка методов и ленивая оценка

После прочтения статьи http://jeffkreefmeijer.com/2011/method-chaining-and-lazy-evaluation-in-ruby/, я начал искать лучшее решение для цепочки методов и отложенных вычислений.

Я думаю, что сформулировал основную проблему с помощью пяти приведенных ниже спецификаций; кто-нибудь может пройти их все?

Все идет: подклассы, делегирование, метапрограммирование, но не рекомендуется для последнего.

Было бы выгодно свести зависимости к минимуму:

require 'rspec'

class Foo
    # Epic code here
end

describe Foo do

  it 'should return an array corresponding to the reverse of the method chain' do
    # Why the reverse? So that we're forced to evaluate something
    Foo.bar.baz.should == ['baz', 'bar']
    Foo.baz.bar.should == ['bar', 'baz']
  end

  it 'should be able to chain a new method after initial evaluation' do
    foobar = Foo.bar
    foobar.baz.should == ['baz', 'bar']

    foobaz = Foo.baz
    foobaz.bar.should == ['bar', 'baz']
  end

  it 'should not mutate instance data on method calls' do
    foobar = Foo.bar
    foobar.baz
    foobar.baz.should == ['baz', 'bar']
  end

  it 'should behave as an array as much as possible' do
    Foo.bar.baz.map(&:upcase).should == ['BAZ', 'BAR']

    Foo.baz.bar.join.should == 'barbaz'

    Foo.bar.baz.inject do |acc, str|
      acc << acc << str
    end.should == 'bazbazbar'

    # === There will be cake! ===
    # Foo.ancestors.should include Array
    # Foo.new.should == []
    # Foo.new.methods.should_not include 'method_missing'
  end

  it "should be a general solution to the problem I'm hoping to solve" do
    Foo.bar.baz.quux.rab.zab.xuuq.should == ['xuuq', 'zab', 'rab', 'quux', 'baz', 'bar']
    Foo.xuuq.zab.rab.quux.baz.bar.should == ['bar', 'baz', 'quux', 'rab', 'zab', 'xuuq']
    foobarbaz = Foo.bar.baz
    foobarbazquux = foobarbaz.quux
    foobarbazquuxxuuq = foobarbazquux.xuuq
    foobarbazquuxzab = foobarbazquux.zab

    foobarbaz.should == ['baz', 'bar']
    foobarbazquux.should == ['quux', 'baz', 'bar']
    foobarbazquuxxuuq.should == ['xuuq', 'quux', 'baz', 'bar']
    foobarbazquuxzab.should == ['zab', 'quux', 'baz', 'bar']
  end

end

person Chris    schedule 26.12.2011    source источник
comment
С какой стати метапрограммирование должно быть обескуражено?   -  person Dave Newton    schedule 26.12.2011
comment
Судя по тому, как устроен язык Ruby, я почти уверен, что не существует класса, который пройдет спецификации в вашем первом блоке it и не пройдет тесты во втором блоке it, если только он не будет действительно причудливым и не использует расширение C с некоторыми ловушками интерпретатора. . Второй блок резервный.   -  person David Grayson    schedule 26.12.2011
comment
Единственная причина, по которой я не одобряю MP, заключается в том, что я не фанат этого, хотя и несколько произвольного ограничения. Я бы предпочел не использовать его, если есть прагматичное решение, которое не требует этого.   -  person Chris    schedule 26.12.2011


Ответы (2)


Тривиально, не так ли?

class Foo < Array
  def self.bar
    other = new
    other << 'bar'
    other
  end
  def self.baz
    other = new
    other << 'baz'
    other
  end
  def bar
    other = clone
    other.unshift 'bar'
    other
  end
  def baz
    other = clone
    other.unshift 'baz'
    other
  end
end

Критерий to_s не работает, потому что 1.9 изменил способ работы Array#to_s. Измените это для совместимости:

Foo.baz.bar.to_s.should == ['bar', 'baz'].to_s

Я хочу торт.

Кстати, метапрограммирование здесь сократит размер кода и значительно повысит гибкость:

class Foo < Array
  def self.method_missing(message, *args)
    other = new
    other << message.to_s
    other
  end
  def method_missing(message, *args)
    other = clone
    other.unshift message.to_s
    other
  end
end
person Amadan    schedule 26.12.2011
comment
В self.method_missing вы можете заменить все три строки просто на new 1, message.to_s. - person David Grayson; 26.12.2011
comment
Я добавил дополнительную спецификацию, чтобы сделать проблему более общей, исключающей вашу первоначальную реализацию. Последний все еще работает, но можете ли вы передать дополнительные спецификации (метод отсутствует)? - person Chris; 26.12.2011
comment
@ChristopherPatuzzo: я достаточно уверен, что вы не сможете найти общее решение без использования method_missing, поскольку мне не известно другого механизма, который бы перехватывал произвольные сообщения. Помимо вашей неприязни, это именно то, для чего создан method_missing. - person Amadan; 26.12.2011
comment
Достаточно справедливо, если это единственное решение - person Chris; 26.12.2011

Это вдохновлено ответом Амадана, но использует меньше строк кода:

class Foo < Array
    def self.method_missing(message, *args)
        new 1, message.to_s
    end
    def method_missing(message, *args)
        dup.unshift message.to_s
    end
end
person David Grayson    schedule 26.12.2011