-
Notifications
You must be signed in to change notification settings - Fork 511
/
MVar.scala
362 lines (329 loc) · 12.6 KB
/
MVar.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
/*
* Copyright (c) 2017-2019 The Typelevel Cats-effect Project Developers
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cats.effect
package concurrent
import cats.effect.concurrent.MVar.{TransformedMVar, TransformedMVar2}
import cats.effect.internals.{MVarAsync, MVarConcurrent}
import cats.~>
import com.github.ghik.silencer.silent
/**
* @define mVarDescription A mutable location, that is either empty or contains a value of type `A`.
*
* It has the following fundamental atomic operations:
*
* - [[put]] which fills the var if empty, or blocks
* (asynchronously) until the var is empty again
* - [[tryPut]] which fills the var if empty. returns true if successful
* - [[take]] which empties the var if full, returning the contained
* value, or blocks (asynchronously) otherwise until there is
* a value to pull
* - [[tryTake]] empties if full, returns None if empty.
* - [[read]] which reads the current value without touching it,
* assuming there is one, or otherwise it waits until a value
* is made available via `put`
* - `tryRead` returns a variable if it exists. Implemented in the successor [[MVar2]]
* - `swap` takes a value, replaces it and returns the taken value. Implemented in the successor [[MVar2]]
* - [[isEmpty]] returns true if currently empty
*
* The `MVar` is appropriate for building synchronization
* primitives and performing simple inter-thread communications.
* If it helps, it's similar with a `BlockingQueue(capacity = 1)`,
* except that it doesn't block any threads, all waiting being
* done asynchronously (via [[Async]] or [[Concurrent]] data types,
* such as [[IO]]).
*
* Given its asynchronous, non-blocking nature, it can be used on
* top of Javascript as well.
*
* Inspired by `Control.Concurrent.MVar` from Haskell and
* by `scalaz.concurrent.MVar`.
*/
sealed private[concurrent] trait MVarDocumentation extends Any {}
/**
* $mVarDescription
*/
@deprecated("`MVar` is now deprecated in favour of a new generation `MVar2` with `tryRead` and `swap` support", "2.2.0")
abstract class MVar[F[_], A] extends MVarDocumentation {
/**
* Returns `true` if the `MVar` is empty and can receive a `put`, or
* `false` otherwise.
*
* Note that due to concurrent tasks, logic built in terms of `isEmpty`
* is problematic.
*/
def isEmpty: F[Boolean]
/**
* Fills the `MVar` if it is empty, or blocks (asynchronously)
* if the `MVar` is full, until the given value is next in
* line to be consumed on [[take]].
*
* This operation is atomic.
*
* @return a task that on evaluation will complete when the
* `put` operation succeeds in filling the `MVar`,
* with the given value being next in line to
* be consumed
*/
def put(a: A): F[Unit]
/**
* Fill the `MVar` if we can do it without blocking,
*
* @return whether or not the put succeeded
*/
def tryPut(a: A): F[Boolean]
/**
* Empties the `MVar` if full, returning the contained value,
* or blocks (asynchronously) until a value is available.
*
* This operation is atomic.
*
* @return a task that on evaluation will be completed after
* a value was retrieved
*/
def take: F[A]
/**
* empty the `MVar` if full
*
* @return an Option holding the current value, None means it was empty
*/
def tryTake: F[Option[A]]
/**
* Tries reading the current value, or blocks (asynchronously)
* until there is a value available.
*
* This operation is atomic.
*
* @return a task that on evaluation will be completed after
* a value has been read
*/
def read: F[A]
/**
* Modify the context `F` using transformation `f`.
*/
@deprecated("`mapK` is deprecated in favor of `imapK` which supports the new invariant `MVar2` interface", "2.2.0")
def mapK[G[_]](f: F ~> G): MVar[G, A] =
new TransformedMVar(this, f)
}
/**
* $mVarDescription
*
* The `MVar2` is the successor of `MVar` with [[tryRead]] and [[swap]]. It was implemented separately only to maintain
* binary compatibility with `MVar`.
*/
@silent("deprecated")
abstract class MVar2[F[_], A] extends MVar[F, A] {
/**
* Replaces a value in MVar and returns the old value.
*
* @note This operation is only safe from deadlocks if there are no other producers for this `MVar`.
*
* @param newValue is a new value
* @return the value taken
*/
def swap(newValue: A): F[A]
/**
* A non-blocking version of [[read]].
*
* @return an Option holding the current value, None means it was empty
*/
def tryRead: F[Option[A]]
/**
* Applies the effectful function `f` on the contents of this `MVar`. In case of failure, it sets the contents of the
* `MVar` to the original value.
*
* @note This operation is only safe from deadlocks if there are no other producers for this `MVar`.
*
* @param f effectful function that operates on the contents of this `MVar`
* @return the value produced by applying `f` to the contents of this `MVar`
*/
def use[B](f: A => F[B]): F[B]
/**
* Modifies the contents of the `MVar` using the effectful function `f`, but also allows for returning a value derived
* from the original contents of the `MVar`. Like [[use]], in case of failure, it sets the contents of the `MVar` to
* the original value.
*
* @note This operation is only safe from deadlocks if there are no other producers for this `MVar`.
*
* @param f effectful function that operates on the contents of this `MVar`
* @return the second value produced by applying `f` to the contents of this `MVar`
*/
def modify[B](f: A => F[(A, B)]): F[B]
/**
* Modifies the contents of the `MVar` using the effectful function `f`. Like [[use]], in case of failure, it sets the
* contents of the `MVar` to the original value.
*
* @note This operation is only safe from deadlocks if there are no other producers for this `MVar`.
*
* @param f effectful function that operates on the contents of this `MVar`
* @return no useful value. Executed only for the effects.
*/
def modify_(f: A => F[A]): F[Unit]
/**
* Modify the context `F` using natural isomorphism `f` with `g`.
*
* @param f functor transformation from `F` to `G`
* @param g functor transformation from `G` to `F`
* @return `MVar2` with a modified context `G` derived using a natural isomorphism from `F`
*/
def imapK[G[_]](f: F ~> G, g: G ~> F): MVar2[G, A] =
new TransformedMVar2(this, f, g)
}
/** Builders for [[MVar]]. */
object MVar {
/**
* Builds an [[MVar]] value for `F` data types that are [[Concurrent]].
*
* Due to `Concurrent`'s capabilities, the yielded values by [[MVar.take]]
* and [[MVar.put]] are cancelable.
*
* This builder uses the
* [[https://typelevel.org/cats/guidelines.html#partially-applied-type-params Partially-Applied Type]]
* technique.
*
* For creating an empty `MVar`:
* {{{
* MVar[IO].empty[Int] <-> MVar.empty[IO, Int]
* }}}
*
* For creating an `MVar` with an initial value:
* {{{
* MVar[IO].init("hello") <-> MVar.init[IO, String]("hello")
* }}}
*
* @see [[of]]and [[empty]]
*/
def apply[F[_]](implicit F: Concurrent[F]): ApplyBuilders[F] =
new ApplyBuilders[F](F)
/**
* Creates a cancelable `MVar` that starts as empty.
*
* @see [[uncancelableEmpty]] for non-cancelable MVars
*
* @param F is a [[Concurrent]] constraint, needed in order to
* describe cancelable operations
*/
def empty[F[_], A](implicit F: Concurrent[F]): F[MVar2[F, A]] =
F.delay(MVarConcurrent.empty)
/**
* Creates a non-cancelable `MVar` that starts as empty.
*
* The resulting `MVar` has non-cancelable operations.
*
* WARN: some `Async` data types, like [[IO]], can be cancelable,
* making `uncancelable` values unsafe. Such values are only useful
* for optimization purposes, in cases where the use case does not
* require cancellation or in cases in which an `F[_]` data type
* that does not support cancellation is used.
*
* @see [[empty]] for creating cancelable MVars
*/
def uncancelableEmpty[F[_], A](implicit F: Async[F]): F[MVar2[F, A]] =
F.delay(MVarAsync.empty)
/**
* Creates a cancelable `MVar` that's initialized to an `initial`
* value.
*
* @see [[uncancelableOf]] for non-cancelable MVars
*
* @param initial is a value that will be immediately available
* for the first `read` or `take` operation
*
* @param F is a [[Concurrent]] constraint, needed in order to
* describe cancelable operations
*/
def of[F[_], A](initial: A)(implicit F: Concurrent[F]): F[MVar2[F, A]] =
F.delay(MVarConcurrent(initial))
/**
* Creates a non-cancelable `MVar` that's initialized to an `initial`
* value.
*
* The resulting `MVar` has non-cancelable operations.
*
* WARN: some `Async` data types, like [[IO]], can be cancelable,
* making `uncancelable` values unsafe. Such values are only useful
* for optimization purposes, in cases where the use case does not
* require cancellation or in cases in which an `F[_]` data type
* that does not support cancellation is used.
*
* @see [[of]] for creating cancelable MVars
*/
def uncancelableOf[F[_], A](initial: A)(implicit F: Async[F]): F[MVar2[F, A]] =
F.delay(MVarAsync(initial))
/**
* Like [[of]] but initializes state using another effect constructor
*/
def in[F[_], G[_], A](initial: A)(implicit F: Sync[F], G: Concurrent[G]): F[MVar2[G, A]] =
F.delay(MVarConcurrent(initial))
/**
* Like [[empty]] but initializes state using another effect constructor
*/
def emptyIn[F[_], G[_], A](implicit F: Sync[F], G: Concurrent[G]): F[MVar2[G, A]] =
F.delay(MVarConcurrent.empty)
/**
* Like [[uncancelableOf]] but initializes state using another effect constructor
*/
def uncancelableIn[F[_], G[_], A](initial: A)(implicit F: Sync[F], G: Async[G]): F[MVar2[G, A]] =
F.delay(MVarAsync(initial))
/**
* Like [[uncancelableEmpty]] but initializes state using another effect constructor
*/
def uncancelableEmptyIn[F[_], G[_], A](implicit F: Sync[F], G: Async[G]): F[MVar2[G, A]] =
F.delay(MVarAsync.empty)
/**
* Returned by the [[apply]] builder.
*/
final class ApplyBuilders[F[_]](val F: Concurrent[F]) extends AnyVal {
/**
* Builds an `MVar` with an initial value.
*
* @see documentation for [[MVar.of]]
*/
def of[A](a: A): F[MVar2[F, A]] =
MVar.of(a)(F)
/**
* Builds an empty `MVar`.
*
* @see documentation for [[MVar.empty]]
*/
def empty[A]: F[MVar2[F, A]] =
MVar.empty(F)
}
final private[concurrent] class TransformedMVar[F[_], G[_], A](underlying: MVar[F, A], trans: F ~> G)
extends MVar[G, A] {
override def isEmpty: G[Boolean] = trans(underlying.isEmpty)
override def put(a: A): G[Unit] = trans(underlying.put(a))
override def tryPut(a: A): G[Boolean] = trans(underlying.tryPut(a))
override def take: G[A] = trans(underlying.take)
override def tryTake: G[Option[A]] = trans(underlying.tryTake)
override def read: G[A] = trans(underlying.read)
}
final private[concurrent] class TransformedMVar2[F[_], G[_], A](underlying: MVar2[F, A],
trans: F ~> G,
inverse: G ~> F)
extends MVar2[G, A] {
override def isEmpty: G[Boolean] = trans(underlying.isEmpty)
override def put(a: A): G[Unit] = trans(underlying.put(a))
override def tryPut(a: A): G[Boolean] = trans(underlying.tryPut(a))
override def take: G[A] = trans(underlying.take)
override def tryTake: G[Option[A]] = trans(underlying.tryTake)
override def read: G[A] = trans(underlying.read)
override def tryRead: G[Option[A]] = trans(underlying.tryRead)
override def swap(newValue: A): G[A] = trans(underlying.swap(newValue))
override def use[B](f: A => G[B]): G[B] = trans(underlying.use(a => inverse(f(a))))
override def modify[B](f: A => G[(A, B)]): G[B] = trans(underlying.modify(a => inverse(f(a))))
override def modify_(f: A => G[A]): G[Unit] = trans(underlying.modify_(a => inverse(f(a))))
}
}