2011-10-24 4 views
26

Estoy pasando por una fase de tratar de evitar las variables temporales y el uso excesivo de condicional, donde puedo usar un estilo de codificación más fluido. Me gustó mucho usar #tap en lugares donde deseo obtener el valor que necesito devolver, pero hago algo con él antes de devolverlo.Método combinatorio como tap, pero capaz de devolver un valor diferente?

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

Vs. el procedimiento:

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 

Obviamente los ejemplos anteriores son simples, pero esto es un estilo de programación bastante común en la comunidad de rubí, no obstante. A veces voy a utilizar #inject pasar un objeto a través de una serie de filtros también:

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

vs. el procedimiento:

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

Ahora estoy frente a un uso repetido de una condición como la siguiente, y en busca de un enfoque más fluido a su manipulación:

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

La solución (ligeramente) más limpio es evitar la variable temporal en el costo de la duplicación:

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 

no puedo evitar sentir el deseo de usar algo casi como#tap aquí sin embargo.

¿Qué otros patrones podría seguir aquí. Me doy cuenta de que todo esto no es más que un sinsentido de azúcar para algunas personas y que debería pasar a problemas más interesantes, pero estoy tratando de aprender a escribir con un estilo más funcional, así que solo tengo curiosidad por saber qué determinaron los rubyistas a largo plazo. ser una buena manera de abordar situaciones como esta. Estos ejemplos están enormemente simplificados.

+3

Con el riesgo de ser pedante, parece que su uso del grifo para inducir efectos secundarios es antifuncional.Los programadores funcionales y los lenguajes evitan o previenen los efectos secundarios. El punto de toque es que no devolverá lo que se ejecuta en él. Por lo tanto, se puede usar de dos maneras: depuración y métodos que inducen efectos secundarios. La forma funcional es simplemente encadenar métodos juntos o componerlos. –

+0

Sin riesgo, me gustaría hablar teoría, aunque me temo que este hilo se cerraría si no hay una pregunta directa, sin embargo, dado que '# update' devolvería un booleano, no el valor de' obj' (que está más allá mi control), ¿no soluciona la necesidad de una tercera expresión para devolver el valor original? Me gustaría entender más técnicas funcionales correctas :) – d11wtq

+0

Ah, veo cómo podría cambiar eso: 'update (something_complex (a, b, c))', donde he definido 'update' para hacer' argument.update (: x => y) '... aunque esto se vuelve más detallado ya que los parámetros de actualización necesitan pasar. – d11wtq

Respuesta

15

Definir Object#as:

class Object 
    def as 
    yield self 
    end 
end 

Y ahora se puede escribir:

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, su truco 'Object # as' es definitivamente mejor (para mis ojos) que' assign; if..else..end' versión. Probablemente podría tener un 'some_method (a, b) .on (a_predicate?) {| Obj | obj.whatever} 'también. Puedo tomar su 'como 'y revertirlo ligeramente, agregando' con' a la persona que llama: 'con (algo_complejo (a, b)) {| obj | a_predicate? obj.whatever: obj} '. Hmmm. – d11wtq

+0

@ d11wtq: De hecho, el {} también se ve bien, por lo general es mejor llamar a un método en lugar de ocultarlo con los contenedores send (: methods) (el problema es que debes darle el nombre). Tenga en cuenta que su con (x) {| x | ...} es, de hecho, Ick's let: http://ick.rubyforge.org/ – tokland

+0

Ick parece interesante, aunque sospecho que no funcionará bien con RSpec, ya que tiene un método 'let', comportándose completamente diferente :) – d11wtq

3

he encontrado un método en la joya facetas que podría ser lo que estás buscando: Kernel#ergo

Así su método original:

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

podría terminar buscando algo como esto:

require 'facets/kernel/ergo' 

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

que tenía que hacer algo como esto y me gusta la respuesta de tokland, pero yo no quería contaminar objetos para el pequeño guión que estaba escribiendo. En su lugar, hice uso de tap en una matriz:

[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 

La magia está en breaktap vuelve otro valor.

Cuestiones relacionadas