rlm@0: ;; Various Operators on Pure Functions rlm@0: ;; rlm@0: ;; Open source Liscence and all that rlm@0: (ns rlm@0: rlm.function-utils rlm@0: "Collection of Operators on Pure Functions" rlm@0: {:author "Robert McIntyre"} rlm@0: (:use [clojure.contrib.profile])) rlm@0: rlm@0: (def void ::void) rlm@0: rlm@0: (comment rlm@0: rlm@0: "Please help me out here. I'm trying to make a higher order function that takes rlm@0: pure functions that are mathmaticaly the same but have different times and returns another rlm@0: function that runs them both and returns the resut of the one that finishes first." rlm@0: rlm@0: rlm@0: "here's a repl session: rlm@0: rlm@0: mobius> " (time (dotimes [_ 500]((mix-futures + +) 40 3 3 3 3 3))) " rlm@0: Elapsed time: 85.792995 msecs rlm@0: nil rlm@0: mobius> " (time (dotimes [_ 500](+ 40 3 3 3 3 3))) " rlm@0: Elapsed time: 6.956338 msecs rlm@0: nil rlm@0: mobius> " (time (dotimes [_ 500]((mix-threads + +) 40 3 3 3 3 3))) " rlm@0: Elapsed time: 706.227065 msecs rlm@0: nil rlm@0: rlm@0: rlm@0: mobius> " (defn fast-five [& args] 5) " rlm@0: #'mobius/fast-five rlm@0: rlm@0: mobius> " (defn slow-five [& args] (Thread/sleep 5000) (println "Oh YEAH!!!!") 5) " rlm@0: #'mobius/slow-five rlm@0: rlm@0: mobius> " (profile (time (dotimes [_ 5000] (thread-five)))) " rlm@0: \"Elapsed time: 12187.981587 msecs\" rlm@0: Name mean min max count sum rlm@0: create-atom 9621 2025 4433928 5000 48106830 rlm@0: create-threads 5156 2724 2880268 5000 25783796 rlm@0: kill-threads 91313 27866 11175718 5000 456567066 rlm@0: loop 2124249 38482 18470781 5000 10621249899 rlm@0: return 1242 838 5309 5000 6212626 rlm@0: start-threads 252985 110348 12272835 5000 1264927953 rlm@0: nil rlm@0: rlm@0: mobius> " (profile (time (dotimes [_ 5000] (future-five)))) " rlm@0: \"Elapsed time: 607.266671 msecs\" rlm@0: Name mean min max count sum rlm@0: create-atom 1472 1047 31708 5000 7363139 rlm@0: create-futures 30539 2514 1330660 5000 152697998 rlm@0: kill-threads 54158 2444 3938833 5000 270792897 rlm@0: loop 81117 8800 6083059 5000 405587516 rlm@0: return 1215 838 618782 5000 6078146 rlm@0: nil rlm@0: rlm@0: rlm@0: rlm@0: What can I improve here, and why is the future version sooo much faster than the rlm@0: thread version? Is there a better way than using loop? rlm@0: " rlm@0: rlm@0: ) rlm@0: rlm@0: rlm@4: (defn race rlm@0: "Takes any number of mathematically equal functions with rlm@0: possibly different run-times and returns a function that rlm@0: runs each in a separate thread, returns the result from rlm@0: the first thread which finishes, and cancels the other threads." rlm@0: {:author "Robert McIntyre"} rlm@0: ([& functions] rlm@0: (fn [& args] rlm@0: (let [result (promise) rlm@0: futures (doall (for [fun functions] rlm@0: (future (deliver result (apply fun args))))) rlm@0: answer @result] rlm@0: (dorun (map future-cancel futures)) rlm@0: answer)))) rlm@0: rlm@4: (defn race-pred rlm@4: "Takes any number of mathematically equal functions with possibly rlm@4: different run-times and returns a function that runs each in a rlm@4: separate thread, and returns the first available result x for rlm@4: which (pred x) returns true (or not-valid, if (pred x) returns rlm@4: false on all the results). Cancels the other threads upon rlm@4: returning early." rlm@3: {:author "Robert McIntyre"} rlm@4: ([pred not-valid & functions] rlm@3: (fn [& args] rlm@3: (let [result (promise) rlm@4: latch (java.util.concurrent.CountDownLatch. rlm@4: (count functions)) rlm@4: failure-case (future (.await latch) rlm@4: (deliver result not-valid)) rlm@4: futures rlm@4: (doall rlm@4: (cons failure-case rlm@4: (for [fun functions] rlm@4: (future rlm@4: (let [answer? (apply fun args)] rlm@4: (if (pred answer?) rlm@4: (deliver result answer?) rlm@4: (.countDown latch))))))) rlm@3: answer @result] rlm@3: (dorun (map future-cancel futures)) rlm@3: answer)))) rlm@0: rlm@0: rlm@0: rlm@0: (defn mix-threads rlm@0: " Takes any number of pure functions that take the same arguments and rlm@0: compute the same value and returns a function that runs each in a separate rlm@0: thread, returns the result from the first thread which finshes, and cancels rlm@0: the other threads. Explicitly uses nasty Threads. rlm@0: rlm@0: For example: rlm@0: (do rlm@0: (defn fun1 [] (Thread/sleep 5000) 5) rlm@0: (defn fun2 [] (Thread/sleep 700000) 5) rlm@0: (time ((mix fun1 fun2)))) rlm@0: rlm@0: Returns: rlm@0: | Elapsed time: 5000.66214 msecs rlm@0: 5" rlm@0: [& functions] rlm@0: (fn [& args] rlm@0: (let [result (prof :create-atom (atom void)) rlm@0: threads rlm@0: (prof :create-threads (map rlm@0: (fn [fun] rlm@0: (Thread. rlm@0: (fn [] rlm@0: (try (let [answer (apply fun args)] rlm@0: (reset! result answer)) rlm@0: (catch Exception _ nil))))) rlm@0: functions))] rlm@0: rlm@0: (prof :start-threads (dorun (map #(.start %) threads))) rlm@0: (prof :loop (loop [] rlm@0: (if (= (deref result) void) rlm@0: (recur) rlm@0: (do (prof :kill-threads (dorun (map #(.stop %) threads))) rlm@0: (prof :return (deref result))) rlm@0: )))))) rlm@0: rlm@0: (defmacro defmix rlm@0: " Defines a function from any number of pure functions that take the same rlm@0: arguments and compute the same value which: rlm@0: rlm@0: Runs each in a separate thread. rlm@0: Returns the result from the first thread which finshes. rlm@0: Cancels the other threads. rlm@0: rlm@0: Use this whenever you want to combine two pure functions that rlm@0: compute the same thing, but use different algorithms with different rlm@0: run times for various inputs. rlm@0: rlm@0: For example: rlm@0: (do rlm@0: (defn fun1 [] (Thread/sleep 5000) 5) rlm@0: (defn fun2 [] (Thread/sleep 700000) 5) rlm@0: (defmix fun3 \"combination of fun1 and fun2\" fun1 fun2) rlm@0: (time (fun3)) rlm@0: rlm@0: Returns: rlm@0: | Elapsed time: 5000.66214 msecs rlm@0: 5" rlm@0: rlm@0: {:arglists '([name doc-string? functions*])} rlm@0: rlm@0: [name & functions] rlm@0: (let [doc-string (if (string? (first functions)) (first functions) "") rlm@0: functions (if (string? (first functions)) (rest functions) functions) rlm@0: arglists (:arglists (meta (resolve (eval `(quote ~(first functions)))))) rlm@0: name (with-meta name (assoc (meta name) :arglists `(quote ~arglists) :doc doc-string))] rlm@0: `(def ~name (mix ~@functions)))) rlm@0: rlm@0: rlm@0: (defn runonce rlm@0: "Decorator. returns a function which will run only once. Inspired rlm@0: by Halloway's version from lancet." rlm@0: {:author "Robert McIntyre"} rlm@0: [function] rlm@0: (let [sentinel (Object.) rlm@0: result (atom sentinel)] rlm@0: (fn [& args] rlm@0: (locking sentinel rlm@0: (if (= @result sentinel) rlm@0: (reset! result (apply function args)) rlm@0: @result))))) rlm@0: rlm@0: rlm@0: rlm@0: ;I'm thinking this will be the docstring for mix eventually. rlm@0: rlm@0: ;; " Takes any number of pure functions that take the same arguments and rlm@0: ;; compute the same value and returns a function that runs each in a separate rlm@0: ;; thread, returns the result from the first thread which finshes, and cancels rlm@0: ;; the other threads. rlm@0: rlm@0: ;; For example: rlm@0: ;; (do rlm@0: ;; (defn fun1 [] (Thread/sleep 5000) 5) rlm@0: ;; (defn fun2 [] (Thread/sleep 700000) 5) rlm@0: ;; (time ((mix fun1 fun2)))) rlm@0: rlm@0: ;; Returns: rlm@0: ;; | Elapsed time: 5000.66214 msecs rlm@0: ;; 5"