2011-12-26 12 views
6

Po przeczytaniu artykułu http://jeffkreeftmeijer.com/2011/method-chaining-and-lazy-evaluation-in-ruby/, zacząłem szukać lepszego rozwiązania do łączenia łańcuchów i leniwego oceniania.Wyzwanie Ruby - Metoda łańcuchowa i Lazy Ocena

Myślę, że udało mi się zamknąć podstawowy problem za pomocą pięciu poniższych specyfikacji; Czy każdy może je wszystkie przechodzić?

Cokolwiek pójdzie: podklasy, delegacja, metaprogramowanie, ale zniechęca do tego drugiego.

Byłoby korzystne, aby utrzymać współzależności do minimum:

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 
+3

Dlaczego na ziemi nie METAPROGRAMOWANIE być zniechęconym? –

+0

Sposób, w jaki zaprojektowano język Ruby, jestem prawie pewien, że nie ma klasy, która przejdzie specyfikacje w twoim pierwszym bloku 'it' i nie przejdzie testów w twoim drugim bloku' it', chyba że jest naprawdę dziwaczny i używa C rozszerzenie z niektórymi hakami interpretującymi. Drugi blok jest zbędny. –

+0

Moim jedynym powodem zniechęcenia MP jest fakt, że nie jestem jej fanem, aczkolwiek nieco arbitralnym ograniczeniem. Wolałbym go nie używać, jeśli istnieje pragmatyczne rozwiązanie, które tego nie wymaga. – Chris

Odpowiedz

3

trywialne, prawda?

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 

Kryterium to_s zawiedzie bo 1,9 zmieniła sposób Array#to_s prace. Zmień to na kompatybilność:

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

Chcę ciasto.

BTW - metaprogramming tutaj by ściąć rozmiaru kodu i zwiększenie elastyczności ogromnie:

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 
+0

W 'self.method_missing' możesz zastąpić wszystkie trzy linie po prostu' new 1, message.to_s'. –

+0

Dodałem dodatkową specyfikację, która sprawia, że ​​problem ogólniej wyklucza twoją początkową implementację. Ten ostatni nadal działa, ale czy możesz uzyskać dodatkowe specyfikacje (brakująca metoda)? – Chris

+1

@ ChristopherPatuzzo: Jestem pewien, że nie możesz zrobić ogólnego rozwiązania bez użycia 'method_missing', ponieważ nie ma innego mechanizmu, o którym jestem świadomy, który będzie łapał dowolne wiadomości. Twoja niechęć na bok, to jest właśnie rzecz, dla której stworzono 'method_missing'. – Amadan

5

ta jest inspirowana odpowiedź Amadan, ale używa mniej linii kodu:

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 
+0

Nice! Naucz się czegoś nowego każdego dnia ... – Amadan