rlm@0
|
1 ;; Various Operators on Pure Functions
|
rlm@0
|
2 ;;
|
rlm@0
|
3 ;; Open source Liscence and all that
|
rlm@0
|
4 (ns
|
rlm@0
|
5 rlm.function-utils
|
rlm@0
|
6 "Collection of Operators on Pure Functions"
|
rlm@0
|
7 {:author "Robert McIntyre"}
|
rlm@0
|
8 (:use [clojure.contrib.profile]))
|
rlm@0
|
9
|
rlm@0
|
10 (def void ::void)
|
rlm@0
|
11
|
rlm@0
|
12 (comment
|
rlm@0
|
13
|
rlm@0
|
14 "Please help me out here. I'm trying to make a higher order function that takes
|
rlm@0
|
15 pure functions that are mathmaticaly the same but have different times and returns another
|
rlm@0
|
16 function that runs them both and returns the resut of the one that finishes first."
|
rlm@0
|
17
|
rlm@0
|
18
|
rlm@0
|
19 "here's a repl session:
|
rlm@0
|
20
|
rlm@0
|
21 mobius> " (time (dotimes [_ 500]((mix-futures + +) 40 3 3 3 3 3))) "
|
rlm@0
|
22 Elapsed time: 85.792995 msecs
|
rlm@0
|
23 nil
|
rlm@0
|
24 mobius> " (time (dotimes [_ 500](+ 40 3 3 3 3 3))) "
|
rlm@0
|
25 Elapsed time: 6.956338 msecs
|
rlm@0
|
26 nil
|
rlm@0
|
27 mobius> " (time (dotimes [_ 500]((mix-threads + +) 40 3 3 3 3 3))) "
|
rlm@0
|
28 Elapsed time: 706.227065 msecs
|
rlm@0
|
29 nil
|
rlm@0
|
30
|
rlm@0
|
31
|
rlm@0
|
32 mobius> " (defn fast-five [& args] 5) "
|
rlm@0
|
33 #'mobius/fast-five
|
rlm@0
|
34
|
rlm@0
|
35 mobius> " (defn slow-five [& args] (Thread/sleep 5000) (println "Oh YEAH!!!!") 5) "
|
rlm@0
|
36 #'mobius/slow-five
|
rlm@0
|
37
|
rlm@0
|
38 mobius> " (profile (time (dotimes [_ 5000] (thread-five)))) "
|
rlm@0
|
39 \"Elapsed time: 12187.981587 msecs\"
|
rlm@0
|
40 Name mean min max count sum
|
rlm@0
|
41 create-atom 9621 2025 4433928 5000 48106830
|
rlm@0
|
42 create-threads 5156 2724 2880268 5000 25783796
|
rlm@0
|
43 kill-threads 91313 27866 11175718 5000 456567066
|
rlm@0
|
44 loop 2124249 38482 18470781 5000 10621249899
|
rlm@0
|
45 return 1242 838 5309 5000 6212626
|
rlm@0
|
46 start-threads 252985 110348 12272835 5000 1264927953
|
rlm@0
|
47 nil
|
rlm@0
|
48
|
rlm@0
|
49 mobius> " (profile (time (dotimes [_ 5000] (future-five)))) "
|
rlm@0
|
50 \"Elapsed time: 607.266671 msecs\"
|
rlm@0
|
51 Name mean min max count sum
|
rlm@0
|
52 create-atom 1472 1047 31708 5000 7363139
|
rlm@0
|
53 create-futures 30539 2514 1330660 5000 152697998
|
rlm@0
|
54 kill-threads 54158 2444 3938833 5000 270792897
|
rlm@0
|
55 loop 81117 8800 6083059 5000 405587516
|
rlm@0
|
56 return 1215 838 618782 5000 6078146
|
rlm@0
|
57 nil
|
rlm@0
|
58
|
rlm@0
|
59
|
rlm@0
|
60
|
rlm@0
|
61 What can I improve here, and why is the future version sooo much faster than the
|
rlm@0
|
62 thread version? Is there a better way than using loop?
|
rlm@0
|
63 "
|
rlm@0
|
64
|
rlm@0
|
65 )
|
rlm@0
|
66
|
rlm@0
|
67
|
rlm@4
|
68 (defn race
|
rlm@0
|
69 "Takes any number of mathematically equal functions with
|
rlm@0
|
70 possibly different run-times and returns a function that
|
rlm@0
|
71 runs each in a separate thread, returns the result from
|
rlm@0
|
72 the first thread which finishes, and cancels the other threads."
|
rlm@0
|
73 {:author "Robert McIntyre"}
|
rlm@0
|
74 ([& functions]
|
rlm@0
|
75 (fn [& args]
|
rlm@0
|
76 (let [result (promise)
|
rlm@0
|
77 futures (doall (for [fun functions]
|
rlm@0
|
78 (future (deliver result (apply fun args)))))
|
rlm@0
|
79 answer @result]
|
rlm@0
|
80 (dorun (map future-cancel futures))
|
rlm@0
|
81 answer))))
|
rlm@0
|
82
|
rlm@4
|
83 (defn race-pred
|
rlm@4
|
84 "Takes any number of mathematically equal functions with possibly
|
rlm@4
|
85 different run-times and returns a function that runs each in a
|
rlm@4
|
86 separate thread, and returns the first available result x for
|
rlm@4
|
87 which (pred x) returns true (or not-valid, if (pred x) returns
|
rlm@4
|
88 false on all the results). Cancels the other threads upon
|
rlm@4
|
89 returning early."
|
rlm@3
|
90 {:author "Robert McIntyre"}
|
rlm@4
|
91 ([pred not-valid & functions]
|
rlm@3
|
92 (fn [& args]
|
rlm@3
|
93 (let [result (promise)
|
rlm@4
|
94 latch (java.util.concurrent.CountDownLatch.
|
rlm@4
|
95 (count functions))
|
rlm@4
|
96 failure-case (future (.await latch)
|
rlm@4
|
97 (deliver result not-valid))
|
rlm@4
|
98 futures
|
rlm@4
|
99 (doall
|
rlm@4
|
100 (cons failure-case
|
rlm@4
|
101 (for [fun functions]
|
rlm@4
|
102 (future
|
rlm@4
|
103 (let [answer? (apply fun args)]
|
rlm@4
|
104 (if (pred answer?)
|
rlm@4
|
105 (deliver result answer?)
|
rlm@4
|
106 (.countDown latch)))))))
|
rlm@3
|
107 answer @result]
|
rlm@3
|
108 (dorun (map future-cancel futures))
|
rlm@3
|
109 answer))))
|
rlm@0
|
110
|
rlm@0
|
111
|
rlm@0
|
112
|
rlm@0
|
113 (defn mix-threads
|
rlm@0
|
114 " Takes any number of pure functions that take the same arguments and
|
rlm@0
|
115 compute the same value and returns a function that runs each in a separate
|
rlm@0
|
116 thread, returns the result from the first thread which finshes, and cancels
|
rlm@0
|
117 the other threads. Explicitly uses nasty Threads.
|
rlm@0
|
118
|
rlm@0
|
119 For example:
|
rlm@0
|
120 (do
|
rlm@0
|
121 (defn fun1 [] (Thread/sleep 5000) 5)
|
rlm@0
|
122 (defn fun2 [] (Thread/sleep 700000) 5)
|
rlm@0
|
123 (time ((mix fun1 fun2))))
|
rlm@0
|
124
|
rlm@0
|
125 Returns:
|
rlm@0
|
126 | Elapsed time: 5000.66214 msecs
|
rlm@0
|
127 5"
|
rlm@0
|
128 [& functions]
|
rlm@0
|
129 (fn [& args]
|
rlm@0
|
130 (let [result (prof :create-atom (atom void))
|
rlm@0
|
131 threads
|
rlm@0
|
132 (prof :create-threads (map
|
rlm@0
|
133 (fn [fun]
|
rlm@0
|
134 (Thread.
|
rlm@0
|
135 (fn []
|
rlm@0
|
136 (try (let [answer (apply fun args)]
|
rlm@0
|
137 (reset! result answer))
|
rlm@0
|
138 (catch Exception _ nil)))))
|
rlm@0
|
139 functions))]
|
rlm@0
|
140
|
rlm@0
|
141 (prof :start-threads (dorun (map #(.start %) threads)))
|
rlm@0
|
142 (prof :loop (loop []
|
rlm@0
|
143 (if (= (deref result) void)
|
rlm@0
|
144 (recur)
|
rlm@0
|
145 (do (prof :kill-threads (dorun (map #(.stop %) threads)))
|
rlm@0
|
146 (prof :return (deref result)))
|
rlm@0
|
147 ))))))
|
rlm@0
|
148
|
rlm@0
|
149 (defmacro defmix
|
rlm@0
|
150 " Defines a function from any number of pure functions that take the same
|
rlm@0
|
151 arguments and compute the same value which:
|
rlm@0
|
152
|
rlm@0
|
153 Runs each in a separate thread.
|
rlm@0
|
154 Returns the result from the first thread which finshes.
|
rlm@0
|
155 Cancels the other threads.
|
rlm@0
|
156
|
rlm@0
|
157 Use this whenever you want to combine two pure functions that
|
rlm@0
|
158 compute the same thing, but use different algorithms with different
|
rlm@0
|
159 run times for various inputs.
|
rlm@0
|
160
|
rlm@0
|
161 For example:
|
rlm@0
|
162 (do
|
rlm@0
|
163 (defn fun1 [] (Thread/sleep 5000) 5)
|
rlm@0
|
164 (defn fun2 [] (Thread/sleep 700000) 5)
|
rlm@0
|
165 (defmix fun3 \"combination of fun1 and fun2\" fun1 fun2)
|
rlm@0
|
166 (time (fun3))
|
rlm@0
|
167
|
rlm@0
|
168 Returns:
|
rlm@0
|
169 | Elapsed time: 5000.66214 msecs
|
rlm@0
|
170 5"
|
rlm@0
|
171
|
rlm@0
|
172 {:arglists '([name doc-string? functions*])}
|
rlm@0
|
173
|
rlm@0
|
174 [name & functions]
|
rlm@0
|
175 (let [doc-string (if (string? (first functions)) (first functions) "")
|
rlm@0
|
176 functions (if (string? (first functions)) (rest functions) functions)
|
rlm@0
|
177 arglists (:arglists (meta (resolve (eval `(quote ~(first functions))))))
|
rlm@0
|
178 name (with-meta name (assoc (meta name) :arglists `(quote ~arglists) :doc doc-string))]
|
rlm@0
|
179 `(def ~name (mix ~@functions))))
|
rlm@0
|
180
|
rlm@0
|
181
|
rlm@0
|
182 (defn runonce
|
rlm@0
|
183 "Decorator. returns a function which will run only once. Inspired
|
rlm@0
|
184 by Halloway's version from lancet."
|
rlm@0
|
185 {:author "Robert McIntyre"}
|
rlm@0
|
186 [function]
|
rlm@0
|
187 (let [sentinel (Object.)
|
rlm@0
|
188 result (atom sentinel)]
|
rlm@0
|
189 (fn [& args]
|
rlm@0
|
190 (locking sentinel
|
rlm@0
|
191 (if (= @result sentinel)
|
rlm@0
|
192 (reset! result (apply function args))
|
rlm@0
|
193 @result)))))
|
rlm@0
|
194
|
rlm@0
|
195
|
rlm@0
|
196
|
rlm@0
|
197 ;I'm thinking this will be the docstring for mix eventually.
|
rlm@0
|
198
|
rlm@0
|
199 ;; " Takes any number of pure functions that take the same arguments and
|
rlm@0
|
200 ;; compute the same value and returns a function that runs each in a separate
|
rlm@0
|
201 ;; thread, returns the result from the first thread which finshes, and cancels
|
rlm@0
|
202 ;; the other threads.
|
rlm@0
|
203
|
rlm@0
|
204 ;; For example:
|
rlm@0
|
205 ;; (do
|
rlm@0
|
206 ;; (defn fun1 [] (Thread/sleep 5000) 5)
|
rlm@0
|
207 ;; (defn fun2 [] (Thread/sleep 700000) 5)
|
rlm@0
|
208 ;; (time ((mix fun1 fun2))))
|
rlm@0
|
209
|
rlm@0
|
210 ;; Returns:
|
rlm@0
|
211 ;; | Elapsed time: 5000.66214 msecs
|
rlm@0
|
212 ;; 5"
|