-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathComplete.scala
90 lines (87 loc) · 2.38 KB
/
Complete.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
package peschke
import _root_.cats.Hash
import _root_.cats.Monoid
import _root_.cats.Show
/** Used as a replacement for returning Unit.
*
* The reason for this is to avoid certain type issues that come up in how erasure interacts with the way the Scala
* compiler handles Unit. Specifically: it transforms any method which returns Unit, but doesn't actually return Unit,
* by inserting the unit value.
*
* Basically, this:
* {{{
* def method: Unit = 1
* }}}
*
* Is actually this:
* {{{
* def method: Unit = {
* 1
* ()
* }
* }}}
*
* The upshot of this is the compiler has a different view of the world than we do.
*
* When refactoring something to have asynchronous side effect, care has to be taken, or bad things can happen.
* {{{
* def getValue: Future[Int] = Future(1)
* def getOtherValue: Future[Int] = Future(2)
*
* def doSideEffectSync(a: Int, b: Int): Unit = logger.info(s"Done: \${a + b}")
*
* def syncVersion: Future[Unit] =
* for {
* a <- getValue
* b <- getOtherValue
* } yield doSideEffectSync(a, b)
*
* def doSideEffectAsync(a: Int, b: Int): Future[Unit] = Future {
* Thread.sleep(10000)
* logger.info(s"Done: \${a + b}")
* }
*
* def asyncVersion: Future[Unit] =
* for {
* a <- getValue
* b <- getOtherValue
* } yield {
* doSideEffectAsync(a, b)
* logger.info("Returning")
* }
*
* logger.info("Synchronous Version")
* Await.result(syncVersion, Inf)
*
* logger.info("\nAsync Version")
* Await.result(asyncVersion, Inf)
*
* logger.info("Finished")
* }}}
*
* Results in this output:
*
* {{{
* Synchronous Version
* Done: 3
*
* Async Version
* Returning
* Finished
* }}}
*
* The program completes before the asynchronous side effect, because it's not tied to the enclosing Future.
*
* Replacing it with Complete avoids this issue, as the incorrect code won't compile.
*/
sealed trait Complete extends Product with Serializable {
def upcast: Complete = this
}
case object Complete extends Complete {
implicit val show: Show[Complete] = Show.show(_ => "Complete")
implicit val hash: Hash[Complete] = Hash.fromUniversalHashCode[Complete]
implicit val monoid: Monoid[Complete] = new Monoid[Complete] {
override def empty: Complete = Complete
override def combine(x: Complete, y: Complete): Complete = Complete
}
}