2011-10-24 10 views
26

Przechodzę przez fazę prób unikania zmiennych tymczasowych i nadużywania warunkowego, w którym mogę użyć bardziej płynnego stylu kodowania. Bardzo podobało mi się używanie #tap w miejscach, w których chcę uzyskać wartość, którą muszę zwrócić, ale zrób coś, zanim zwrócę ją.Kombinatorowa metoda, taka jak dotknij, ale może zwrócić inną wartość?

def fluid_method 
    something_complicated(a, b, c).tap do |obj| 
    obj.update(:x => y) 
    end 
end 

Vs. proceduralnych:

def non_fluid_method 
    obj = something_complicated(a, b, c) 
    obj.update(:x => y) 
    obj # <= I don't like this, if it's avoidable 
end 

Oczywiście powyższe przykłady są proste, ale jest to dość powszechne kodowania styl we wspólnocie rubinowym jednak. Ja czasami użyć #inject aby przekazać obiekt przez serię filtrów TOO:

things.inject(whatever) do |obj, thing| 
    thing.filter(obj) 
end 

Vs. proceduralnych:

obj = whatever 
things.each do |thing| 
    obj = thing.filter(obj) 
end 
obj 

Teraz jestem w obliczu powtarzanego stosowania warunku jak na poniższej ilustracji, a patrząc na podejście bardziej płynu do przenoszenia go:

def not_nice_method 
    obj = something_complex(a, b, c) 
    if a_predicate_check? 
    obj.one_more_method_call 
    else 
    obj 
    end 
end 

Rozwiązanie (nieznacznie) Cleaner jest do uniknąć tymczasową zmienną kosztem powielania:

def not_nice_method 
    if a_predicate_check? 
    something_complex(a, b, c).one_more_method_call 
    else 
    something_complex(a, b, c) 
    end 
end 

nie mogę pomóc, ale czuje chęć korzystania coś prawie jak#tap tutaj.

Jakie inne wzory mogę tutaj śledzić. Zdaję sobie sprawę, że jest to po prostu bezsensowny cukier dla niektórych osób i że powinienem po prostu przejść do bardziej interesujących problemów, ale próbuję nauczyć się pisać w bardziej funkcjonalnym stylu, więc jestem ciekawy, co ustalili długoterminowi rubiści być dobrym sposobem radzenia sobie z takimi sytuacjami. Te przykłady są bardzo uproszczone.

+3

Na ryzyko bycia pedantycznym wydaje się, że używanie kranu do wywoływania efektów ubocznych jest anty-funkcjonalne.Funkcjonalni programiści i języki unikają lub zapobiegają skutkom ubocznym. Punktem dotknięcia jest to, że nie zwróci to, co zostanie w nim wykonane. W związku z tym można go zastosować na dwa sposoby: debugowanie i metody wywołujące efekty uboczne. Funkcjonalny sposób polega po prostu na łączeniu metod lub łączeniu ich. –

+0

Bez ryzyka, chciałbym mówić o teorii, choć obawiam się, że ten wątek zostanie zamknięty, jeśli nie będzie bezpośredniego pytania, ponieważ '# update' zwróci wartość logiczną, a nie wartość' obj' (która jest poza moja kontrola), nie dotyka rozwiązania, aby trzecie wyrażenie nie zwróciło pierwotnej wartości? Chciałbym zrozumieć bardziej poprawne techniki funkcjonalne :) – d11wtq

+0

Ach, widzę, jak mogłem to zmienić: 'update (coś_complex (a, b, c))', gdzie zdefiniowałem 'update', aby zrobić' argument.update (: x => y) '... chociaż staje się to bardziej szczegółowe, ponieważ parametry aktualizacji muszą zostać przekazane. – d11wtq

Odpowiedz

15

Definiowanie Object#as:

class Object 
    def as 
    yield self 
    end 
end 

I teraz można napisać:

def not_sure_this_is_nice_enough_method1 
    something_complex(a, b, c).as do |obj| 
    a_predicate_check? ? obj.one_more_method_call : obj 
    end 
end 
+2

Ohhh, twoja sztuczka 'Object # as' jest zdecydowanie lepsza (w moich oczach) niż' assign; if..else..end' version. Prawdopodobnie możesz mieć 'some_method (a, b) .on (a_predicate?) {| Obj | obj.whatever} 'too. Mogę wziąć twoje 'as' i odwrócić go lekko, dodając' z' do wywołującego: 'with (something_complex (a, b)) {| obj | a_predicate? obj.whatever: obj} '. Hmmm. – d11wtq

+0

@ d11wtq: Rzeczywiście, na {} wygląda również ładnie, zwykle lepiej jest wywołać metodę zamiast ukrywania jej za pomocą wrapperów (sendes): (problem polega na tym, że musisz podać nazwę). Zauważ, że twoje z (x) {| x | ...} tak naprawdę to niech będzie: http://ick.rubyforge.org/ – tokland

+0

Ick wygląda interesująco, choć podejrzewam, że nie będzie dobrze grał z RSpec, ponieważ ma własną metodę "let", zachowuje się zupełnie inaczej :) – d11wtq

3

znalazłem metodę w gem aspektów, które może być to, czego szukali: Kernel#ergo

So Twoja oryginalna metoda:

def not_nice_method 
    obj = something_complex(a, b, c) 
    if a_predicate_check? 
    obj.one_more_method_call 
    else 
    obj 
    end 
end 

może w końcu wygląda mniej więcej tak:

require 'facets/kernel/ergo' 

def nice_method 
    something_complex(a, b, c).ergo do |_| 
    a_predicate_check? ? _.one_more_method_call : _ 
    end 
end 
0

muszę zrobić coś takiego i lubię odpowiedź tokland, ale nie chciałem zanieczyszczać obiektu dla małego skryptu pisałem. Zamiast tego wykorzystały tap na tablicy:

[something_complicated].tap { |s| s[0] = new_cool_thing)}.first 
0
def best_nice_method 
    something_complex(a, b, c).tap |obj| 
    break obj.one_more_method_call if a_predicate_check? 
    end 
end 

Magia jest break w tap zwraca inną wartość.

Powiązane problemy