Skip to content

Instantly share code, notes, and snippets.

@rlm
Created July 20, 2010 23:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rlm/483772 to your computer and use it in GitHub Desktop.
Save rlm/483772 to your computer and use it in GitHub Desktop.
;; Various Operators on Pure Functions
;;
;; Open source Liscence and all that
(ns
"Collection of Operators on Pure Functions"
{:author "Robert McIntyre"}
mobius.utils.function-utils
(:use [clojure.contrib.profile]))
(comment
"Please help me out here. I'm trying to make a higher order function that takes
pure functions that are mathmaticaly the same but have different times and returns another
function that runs them both and returns the resut of the one that finishes first."
"here's a repl session:
mobius> " (time (dotimes [_ 500]((mix-futures + +) 40 3 3 3 3 3))) "
Elapsed time: 85.792995 msecs
nil
mobius> " (time (dotimes [_ 500](+ 40 3 3 3 3 3))) "
Elapsed time: 6.956338 msecs
nil
mobius> " (time (dotimes [_ 500]((mix-threads + +) 40 3 3 3 3 3))) "
Elapsed time: 706.227065 msecs
nil
mobius> " (defn fast-five [& args] 5) "
#'mobius/fast-five
mobius> " (defn slow-five [& args] (Thread/sleep 5000) (println "Oh YEAH!!!!") 5) "
#'mobius/slow-five
mobius> " (profile (time (dotimes [_ 5000] (thread-five)))) "
\"Elapsed time: 12187.981587 msecs\"
Name mean min max count sum
create-atom 9621 2025 4433928 5000 48106830
create-threads 5156 2724 2880268 5000 25783796
kill-threads 91313 27866 11175718 5000 456567066
loop 2124249 38482 18470781 5000 10621249899
return 1242 838 5309 5000 6212626
start-threads 252985 110348 12272835 5000 1264927953
nil
mobius> " (profile (time (dotimes [_ 5000] (future-five)))) "
\"Elapsed time: 607.266671 msecs\"
Name mean min max count sum
create-atom 1472 1047 31708 5000 7363139
create-futures 30539 2514 1330660 5000 152697998
kill-threads 54158 2444 3938833 5000 270792897
loop 81117 8800 6083059 5000 405587516
return 1215 838 618782 5000 6078146
nil
What can I improve here, and why is the future version sooo much faster than the
thread version? Is there a better way than using loop?
"
)
(defn mix-futures
" Takes any number mathematically identical pure functions and returns a function
that runs each in a separate thread, returns the result from the first thread which finishes,
and cancels the other threads. Uses futures which are more elegant."
[& functions]
(fn [& args]
(let [result (prof :create-atom (atom ::void))
futures (prof :create-futures (doall (map
(fn [fun](future
(reset! result (apply fun args)))) functions)))]
(prof :loop (loop []
(if (= (deref result) void)
(recur)
(do (prof :future-cancel (dorun (map future-cancel futures)))
(prof :return (deref result)))
))))))
(defn mix-threads
" Takes any number of pure functions that take the same arguments and
compute the same value and returns a function that runs each in a separate
thread, returns the result from the first thread which finshes, and cancels
the other threads. Explicitly uses nasty Threads.
For example:
(do
(defn fun1 [] (Thread/sleep 5000) 5)
(defn fun2 [] (Thread/sleep 700000) 5)
(time ((mix fun1 fun2))))
Returns:
| Elapsed time: 5000.66214 msecs
5"
[& functions]
(fn [& args]
(let [result (prof :create-atom (atom ::void))
threads
(prof :create-threads (map
(fn [fun]
(Thread.
(fn []
(try (let [answer (apply fun args)]
(reset! result answer))
(catch Exception _ nil)))))
functions))]
(prof :start-threads (dorun (map #(.start %) threads)))
(prof :loop (loop []
(if (= (deref result) void)
(recur)
(do (prof :kill-threads (dorun (map #(.stop %) threads)))
(prof :return (deref result)))
))))))
(defmacro defmix
" Defines a function from any number of pure functions that take the same
arguments and compute the same value which:
Runs each in a separate thread.
Returns the result from the first thread which finshes.
Cancels the other threads.
Use this whenever you want to combine two pure functions that
compute the same thing, but use different algorithms with different
run times for various inputs.
For example:
(do
(defn fun1 [] (Thread/sleep 5000) 5)
(defn fun2 [] (Thread/sleep 700000) 5)
(defmix fun3 \"combination of fun1 and fun2\" fun1 fun2)
(time (fun3))
Returns:
| Elapsed time: 5000.66214 msecs
5"
{:arglists '([name doc-string? functions*])}
[name & functions]
(let [doc-string (if (string? (first functions)) (first functions) "")
functions (if (string? (first functions)) (rest functions) functions)
arglists (:arglists (meta (resolve (eval `(quote ~(first functions))))))
name (with-meta name (assoc (meta name) :arglists `(quote ~arglists) :doc doc-string))]
`(def ~name (mix ~@functions))))
;I'm thinking this will be the docstring for mix eventually.
;; " Takes any number of pure functions that take the same arguments and
;; compute the same value and returns a function that runs each in a separate
;; thread, returns the result from the first thread which finshes, and cancels
;; the other threads.
;; For example:
;; (do
;; (defn fun1 [] (Thread/sleep 5000) 5)
;; (defn fun2 [] (Thread/sleep 700000) 5)
;; (time ((mix fun1 fun2))))
;; Returns:
;; | Elapsed time: 5000.66214 msecs
;; 5"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment