2011-08-30 12 views
9

Tengo un entrante de líneas de flujo perezosas de un archivo que estoy leyendo con tail-seq (contrib - ahora!) Y quiero procesar esas líneas una tras otra con varias "funciones de escucha" que toma acción que depende de hits de re-seq (u otras cosas) en las líneas.Un argumento, muchas funciones

He intentado lo siguiente:

(defn info-listener [logstr] 
    (if (re-seq #"INFO" logstr) (println "Got an INFO-statement"))) 

(defn debug-listener [logstr] 
    (if (re-seq #"DEBUG" logstr) (println "Got a DEBUG-statement"))) 

(doseq [line (tail-seq "/var/log/any/java.log")] 
    (do (info-listener logstr) 
     (debug-listener logstr))) 

y funciona como se esperaba. Sin embargo, hay un montón de duplicación de código y otros pecados en el código, y es aburrido actualizar el código.

Un paso importante parece ser la aplicación de muchas de las funciones de un argumento, es decir

(listen-line line '(info-listener debug-listener)) 

y el uso que en lugar de la declaración propensos do-aburrido y error.

He intentado el siguiente método aparentemente inteligente:

(defn listen-line [logstr listener-collection] 
    (map #(% logstr) listener-collection)) 

pero esto sólo sirve para hacer

(nil) (nil) 

hay funciones de clase lazyiness o primero me pican a ciencia cierta, pero ¿dónde pongo el ¿aplicar?

También estoy abierto a un enfoque radicalmente diferente al problema, pero esta parece ser una manera bastante sensata para empezar. Las macros/métodos múltiples parecen ser excesivos/incorrectos por ahora.

Respuesta

10

Realización de una sola función de un grupo de funciones para ser llamado con el mismo argumento se puede hacer con la función básica juxt:

 
=>(def juxted-fn (juxt identity str (partial/100))) 
=>(juxted-fn 50) 
[50 "50" 2] 

Combinando juxt con partial puede ser muy útil:

 
(defn listener [re message logstr] 
    (if (re-seq re logstr) (println message))) 
 
(def juxted-listener 
    (apply juxt (map (fn [[re message]] (partial listner re message)) 
    [[#"INFO","Got INFO"], 
     [#"DEBUG", "Got DEBUG"]])) 
 
(doseq [logstr ["INFO statement", "OTHER statement", "DEBUG statement"]] 
    (juxted-listener logstr)) 

+0

Esto es muy elegante y también me dio una idea más profunda de Clojure (al igual que la respuesta anterior). Gracias, Alex! – claj

+1

@claj eres muy amable. Si desea llevar más allá el nivel de abstracción, realice un pequeño cambio y tenga el segundo argumento para que el oyente sea una función para llamar con logstr. Eso permite diferentes acciones basadas en la coincidencia de entrada. En ese momento, básicamente hemos reinventado un multimétodo. ¡Las funciones como entidades de primera clase permiten abstracciones muy potentes! –

9

necesita cambiar

(listen-line line '(info-listener debug-listener)) 

a

(listen-line line [info-listener debug-listener]) 

En la primera versión, listen-line termina utilizando los símbolos info-listener y debug-listener sí mismos como funciones debido a la cotización. Los símbolos implementan clojure.lang.IFn (la interfaz detrás de la invocación de la función Clojure) como lo hacen las palabras clave, es decir, se ven en un argumento tipo mapa (en realidad, clojure.lang.ILookup) y devuelven nil si se aplican a algo que no es un mapa.

También tenga en cuenta que es necesario para envolver el cuerpo de listen-line en dorun para asegurar que en realidad es ejecutado (como map devuelve una secuencia perezoso). Mejor aún, cambie a doseq:

(defn listen-line [logstr listener-collection] 
    (doseq [listener listener-collection] 
    (listener logstr))) 
+3

mejor, mejor aún, cambiar a 'Juxt ':' ((aplicar juxt listener-collection) logstr) ' – amalloy

+0

¡gracias Michal por ayudarme a aclarar mi error con las listas!Creo que juxt es la solución más idiomática para mí, ¡gracias amalloy arriba y Alex a continuación también por esa sugerencia! – claj

Cuestiones relacionadas