Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

One argument, many functions

Tags:

clojure

I have an incoming lazy stream lines from a file I'm reading with tail-seq (to contrib - now!) and I want to process those lines one after one with several "listener-functions" that takes action depending on re-seq-hits (or other things) in the lines.

I tried the following:

(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)))

and it works as expected. However, there is a LOT of code-duplication and other sins in the code, and it's boring to update the code.

One important step seems to be to apply many functions to one argument, ie

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

and use that instead of the boring and error prone do-statement.

I've tried the following seemingly clever approach:

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

but this only renders

(nil) (nil)

there is lazyiness or first class functions biting me for sure, but where do I put the apply?

I'm also open to a radically different approach to the problem, but this seems to be a quite sane way to start with. Macros/multi methods seems to be overkill/wrong for now.

like image 891
claj Avatar asked Aug 30 '11 17:08

claj


2 Answers

Making a single function out of a group of functions to be called with the same argument can be done with the core function juxt:

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

Combining juxt with partial can be very useful:

(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))

like image 58
Alex Stoddard Avatar answered Oct 06 '22 09:10

Alex Stoddard


You need to change

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

to

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

In the first version, listen-line ends up using the symbols info-listener and debug-listener themselves as functions because of the quoting. Symbols implement clojure.lang.IFn (the interface behind Clojure function invocation) like keywords do, i.e. they look themselves up in a map-like argument (actually a clojure.lang.ILookup) and return nil if applied to something which is not a map.

Also note that you need to wrap the body of listen-line in dorun to ensure it actually gets executed (as map returns a lazy sequence). Better yet, switch to doseq:

(defn listen-line [logstr listener-collection]
  (doseq [listener listener-collection]
    (listener logstr)))
like image 27
Michał Marczyk Avatar answered Oct 06 '22 10:10

Michał Marczyk