UNPKG

9.39 kBPlain TextView Raw
1# # The Future monad
2#
3# The `Future(a, b)` monad represents values that depend on time. This
4# allows one to model time-based effects explicitly, such that one can
5# have full knowledge of when they're dealing with delayed computations,
6# latency, or anything that can not be computed immediately.
7#
8# A common use for this monad is to replace the usual
9# Continuation-Passing Style form of programming, in order to be
10# able to compose and sequence time-dependent effects using the generic
11# and powerful monadic operations.
12
13/** ^
14 * Copyright (c) 2013 Quildreen Motta
15 *
16 * Permission is hereby granted, free of charge, to any person
17 * obtaining a copy of this software and associated documentation files
18 * (the "Software"), to deal in the Software without restriction,
19 * including without limitation the rights to use, copy, modify, merge,
20 * publish, distribute, sublicense, and/or sell copies of the Software,
21 * and to permit persons to whom the Software is furnished to do so,
22 * subject to the following conditions:
23 *
24 * The above copyright notice and this permission notice shall be
25 * included in all copies or substantial portions of the Software.
26 *
27 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
28 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
29 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
30 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
31 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
32 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
33 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
34 */
35
36
37# ## Function: memoised-fork
38#
39# A function that memoises the result of a future operation, and updates
40# the future with the relevant information for the `Show` and `Eq`
41# typeclass implementations.
42#
43# + type: ((a) -> Unit), (b) -> Unit)), Future(a, b) -> ((a) -> Unit, (b) -> Unit)
44memoised-fork = (f, future) ->
45 pending = []
46 started = false
47 resolved = false
48 rejected = false
49
50 # The fold applies the correct operation to the future's value, if the
51 # future has been resolved. Or we run the operation instead.
52 #
53 # For optimisation purposes, we cache the result of the operation, so
54 # if we started an operation before, we mark it as started and push
55 # any subsequent forks into a pending queue that will be invoked once
56 # the original fork returns.
57 return fold = (g, h) ->
58 | resolved => h future.value
59 | rejected => g future.value
60 | started => do
61 pending.push rejected: g, resolved: h
62 void
63 | otherwise => do
64 started = true
65 f do
66 * (a) -> do
67 future.is-pending := false
68 future.is-rejected := rejected := true
69 future.value := a
70 invoke-pending \rejected, a
71 g a
72 * (b) -> do
73 future.is-pending := false
74 future.is-resolved := resolved := true
75 future.value := b
76 invoke-pending \resolved, b
77 h b
78
79 function invoke-pending(kind, value)
80 xs = pending
81 started := false
82 pending.length := 0
83 for x in xs => x[kind] value
84
85
86# ## Function: id
87#
88# The identity function.
89#
90# + type: a -> a
91id = (a) -> a
92
93# ## Excepction: RejectedFutureExtractionError
94#
95# Thrown when trying to extract the value of a rejected future.
96class RejectedFutureExtractionError extends TypeError
97 -> super "Can't extract the value of a rejected future."
98
99
100
101# ## Class: Future
102#
103# The `Future(a, b)` monad.
104class Future
105 # ### Section: Constructors ##########################################
106
107 # #### Constructor
108 #
109 # Creates a new Future for a long-running computation `f`.
110 #
111 # + type: ((a -> Unit), (b -> Unit)) -> Promise(a, b)
112 (f) -> @fork = memoised-fork f, this
113
114
115 # ### Section: Predicates ############################################
116
117 # #### Field: is-pending
118 #
119 # True if the Future hasn't been resolved yet.
120 #
121 # + type: Boolean
122 is-pending: true
123
124 # #### Field: is-resolved
125 #
126 # True if the Future has been resolved successfully.
127 #
128 # + type: Boolean
129 is-resolved: false
130
131 # #### Field: is-failure
132 #
133 # True if the Future has been rejected — resolved with a failure.
134 #
135 # + type: Boolean
136 is-rejected: false
137
138
139 # ### Section: Applicative ###########################################
140
141 # #### Function: of
142 #
143 # Constructs a new Future containing the single value `b`.
144 #
145 # `b` can be any value, including `null`, `undefined` or another
146 # `Future(a, b)` monad.
147 #
148 # + type: b -> Future(a, b)
149 @of = (b) -> new Future (reject, resolve) -> resolve b
150 of: (b) -> Future.of b
151
152 # #### Function: ap
153 #
154 # Applies the function inside the Future to an applicative.
155 #
156 # + type: (@Future(a, b -> c), f:Applicative) => f(b) -> f(c)
157 ap: (b) -> @chain (f) -> b.map f
158
159
160 # ### Section: Functor ###############################################
161
162 # #### Function: map
163 #
164 # Transforms the successful value of the Future using a regular unary
165 # function.
166 #
167 # + type: (@Future(a, b)) => (b -> c) -> Future(a, c)
168 map: (f) -> @chain (a) -> Future.of (f a)
169
170
171 # ### Section: Chain #################################################
172
173 # #### Function: chain
174 #
175 # Transforms the successful value of the Future using a function to a
176 # monad of the same type.
177 #
178 # + type: (@Future(a, b)) => (b -> Future(a, c)) -> Future(a, c)
179 chain: (f) ->
180 new Future (reject, resolve) ~> @fork do
181 * (a) -> reject a
182 * (b) -> (f b).fork reject, resolve
183
184
185 # ### Section: Show ##################################################
186
187 # #### Function: to-string
188 #
189 # Returns a textual representation of the Future monad.
190 #
191 # + type: (@Future(a, b)) => Unit -> String
192 to-string: ->
193 | @is-pending => "Future.Pending"
194 | @is-resolved => "Future.Resolved(#{@value})"
195 | @is-rejected => "Future.Rejected(#{@value})"
196
197
198 # ### Section: Eq ####################################################
199
200 # #### Function: is-equal
201 #
202 # Tests if an Future monad is equal to another Future monad.
203 #
204 # Equality with pending futures is only decidable if the computation
205 # of both futures can be resolved synchronously. Otherwise this
206 # function will return false.
207 #
208 # + type: (@Future(a, b)) => Future(a, b) -> Boolean
209 is-equal: (a) -> Boolean switch
210 | @is-resolved => a.is-resolved and (a.value is @value)
211 | @is-rejected => a.is-rejected and (a.value is @value)
212 | @is-pending => @fork do
213 * (e) -> a.fork do
214 * (e2) -> e is e2
215 * (_) -> false
216 * (s) -> a.fork do
217 * (_) -> false
218 * (s2) -> s is s2
219
220
221 # ### Section: Extracting and Recovering #############################
222
223 # #### Function: or-else
224 #
225 # Transforms a failure value into a new Future monad. Does nothing if
226 # the monad contains a successful value.
227 #
228 # + type: (@Future(a, b)) => (a -> Future(c, b)) -> Future(c, b)
229 or-else: (f) ->
230 new Future (reject, resolve) ~> @fork do
231 * (a) -> (f a).fork reject, resolve
232 * (b) -> resolve b
233
234 # ### Section: Folds and Extended Transformations ####################
235
236 # #### Function: fold
237 #
238 # Catamorphism. Takes two functions, applies the leftmost one to the
239 # failure value and the rightmost one to the successful value,
240 # depending on which one is present.
241 #
242 # + type: (@Future(a, b)) => (a -> c) -> (b -> c) -> Future(d, c)
243 fold: (f, g) --> new Future (reject, resolve) ~> @fork do
244 * (a) -> resolve (f a)
245 * (b) -> resolve (g b)
246
247
248 # #### Function: swap
249 #
250 # Swaps the disjunction values.
251 #
252 # + type: (@Future(a, b)) => Unit -> Future(b, a)
253 swap: -> new Future (reject, resolve) ~> @fork do
254 * (a) -> resolve a
255 * (b) -> reject b
256
257 # #### Function: bimap
258 #
259 # Maps both sides of the disjunction.
260 #
261 # + type: (@Future(a, b)) => (a -> c) -> (b -> d) -> Future(c, d)
262 bimap: (f, g) --> new Future (reject, resolve) ~> @fork do
263 * (a) -> reject (f a)
264 * (b) -> resolve (g b)
265
266 # #### Function: rejected-map
267 #
268 # Maps the left side of the disjunction (failure).
269 #
270 # + type: (@Future(a, b)) => (a -> c) -> Future(c, b)
271 rejected-map: (f) -> new Future (reject, resolve) ~> @fork do
272 * (a) -> reject (f a)
273 * (b) -> resolve b
274
275
276# ## Exports
277module.exports = Future