-
Notifications
You must be signed in to change notification settings - Fork 120
/
00-intro.scala
379 lines (322 loc) · 10.3 KB
/
00-intro.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
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
package net.degoes.zio
import zio._
/*
* INTRODUCTION
*
* ZIO effects are immutable data values that model a possibly complex series
* of async, concurrent, resourceful, and contextual computations.
*
* The only effect type in ZIO is called ZIO, and has three type parameters,
* which permit accessing context from an environment (`R`), failing with a
* value of a certain type (`E`), and succeeding with a value of a certain
* type (`A`).
*
* Unlike Scala's Future, ZIO effects are completely lazy. All methods on ZIO
* effects return new ZIO effects. No part of the workflow is executed until
* one of the `unsafeRun*` functions are called.
*
* ZIO effects are transformed and combined using methods on the ZIO data type.
* For example, two effects can be combined into a sequential workflow using
* an operator called `zip`. Similarly, two effects can be combined into a
* parallel workflow using an operator called `zipPar`.
*
* The operators on the ZIO data type allow very powerful, expressive, and
* type-safe transformation and composition, while the methods in the ZIO
* companion object allow building new effects from simple values (which are
* not themselves effects).
*
* In this section, you will explore both the ZIO data model itself, as well
* as the very basic operators used to transform and combine ZIO effects, as
* well as a few simple ways to build effects.
*/
/**
* A good mental model for ZIO[R, E, A] is:
* {{{
* ZEnvironment[R] => Either[E, A]
* }}}
* This can be interpreted as a function which, given a ZIO environment
* (which is a map that contain classes of different types), a ZIO
* returns either a failure of type E or a success of type A.
*/
object ZIOModel {
/**
* EXERCISE
*
* Implement all missing methods on the ZIO companion object.
*/
object ZIO {
def succeed[A](success: => A): ZIO[Any, Nothing, A] = ???
def fail[E](error: => E): ZIO[Any, E, Nothing] = ???
def attempt[A](code: => A): ZIO[Any, Throwable, A] = ???
def environment[R]: ZIO[R, Nothing, ZEnvironment[R]] = ???
}
/**
* EXERCISE
*
* Implement all missing methods on the ZIO class.
*/
final case class ZIO[-R, +E, +A](run: ZEnvironment[R] => Either[E, A]) { self =>
def map[B](f: A => B): ZIO[R, E, B] = ???
def flatMap[R1 <: R, E1 >: E, B](f: A => ZIO[R1, E1, B]): ZIO[R1, E1, B] =
???
def zip[R1 <: R, E1 >: E, B](that: ZIO[R1, E1, B]): ZIO[R1, E1, (A, B)] =
???
def either: ZIO[R, Nothing, Either[E, A]] = ???
def provide(r: ZEnvironment[R]): ZIO[Any, E, A] = ???
def orDie(implicit ev: E <:< Throwable): ZIO[R, Nothing, A] =
ZIO(r => self.run(r).fold(throw _, Right(_)))
}
def printLine(line: String): ZIO[Any, Nothing, Unit] =
ZIO.attempt(println(line)).orDie
val readLine: ZIO[Any, Nothing, String] =
ZIO.attempt(scala.io.StdIn.readLine()).orDie
def run[A](zio: ZIO[Any, Throwable, A])(implicit unsafe: Unsafe): A =
zio.run(ZEnvironment.empty).fold(throw _, a => a)
/**
* Run the following main function and compare the results with your
* expectations.
*/
def main(args: Array[String]): Unit =
Unsafe.unsafe { implicit u =>
run {
printLine("Hello, what is your name?").flatMap(
_ => readLine.flatMap(name => printLine(s"Your name is: ${name}"))
)
}
}
}
object ZIOTypes {
type ??? = Nothing
/**
* EXERCISE
*
* Provide definitions for the ZIO type aliases below.
*/
type Task[+A] = ???
type UIO[+A] = ???
type RIO[-R, +A] = ???
type IO[+E, +A] = ???
type URIO[-R, +A] = ???
}
object SuccessEffect extends ZIOAppDefault {
/**
* EXERCISE
*
* Using `ZIO.succeed`, create an effect that succeeds with the string
* "Hello World".
*/
val run =
???
}
object HelloWorld extends ZIOAppDefault {
/**
* EXERCISE
*
* Implement a simple "Hello World!" program by invoking `Console.printLine`
* to create an effect that, when executed, will print out "Hello World!" to
* the console.
*/
val run =
???
}
object SimpleMap extends ZIOAppDefault {
import Console.readLine
/**
* EXERCISE
*
* Using `ZIO#map`, map the string success value of `Console.readLine` into an
* integer (the length of the string)`.
*/
val run =
???
}
object PrintSequenceZip extends ZIOAppDefault {
/**
* EXERCISE
*
* Using `zip`, compose a sequence of `Console.printLine` effects to produce an effect
* that prints three lines of text to the console.
*/
val run =
???
}
object PrintSequence extends ZIOAppDefault {
/**
* EXERCISE
*
* Using `*>` (`zipRight`), compose a sequence of `Console.printLine` effects to
* produce an effect that prints three lines of text to the console.
*/
val run =
???
}
object PrintReadSequence extends ZIOAppDefault {
/**
* EXERCISE
*
* Using `*>` (`zipRight`), sequentially compose a `Console.printLine` effect, which
* models printing out "Hit Enter to exit...", together with a `Console.readLine`
* effect, which models reading a line of text from the console.
*/
val run =
???
}
object SimpleDuplication extends ZIOAppDefault {
/**
* EXERCISE
*
* In the following program, the expression `Console.printLine("Hello again")`
* appears three times. Factor out this duplication by introducing a new
* value that stores the expression, and then referencing that variable
* three times.
*/
val run = {
Console.printLine("Hello") *>
Console.printLine("Hello again") *>
Console.printLine("Hello again") *>
Console.printLine("Hello again")
}
}
object FlatMap extends ZIOAppDefault {
/**
* EXERCISE
*
* The following program is intended to ask the user for their name, then
* read their name, then print their name back out to the user. However,
* the `zipRight` (`*>`) operator is not powerful enough to solve this
* problem, because it does not allow a _subsequent_ effect to depend
* on the success value produced by a _preceding_ effect.
*
* Solve this problem by using the `ZIO#flatMap` operator, which composes
* a first effect together with a "callback", which can return a second
* effect that depends on the success value produced by the first effect.
*/
val run =
Console.printLine("What is your name?") *>
Console.readLine *> // Use .flatMap(...) here
Console.printLine("Your name is: ")
}
object PromptName extends ZIOAppDefault {
/**
* EXERCISE
*
* The following program uses a combination of `zipRight` (`*>`), and
* `flatMap`. However, this makes the structure of the program harder
* to understand. Replace all `zipRight` by `flatMap`, by ignoring the
* success value of the left hand effect.
*/
val run =
Console.printLine("What is your name?") *>
Console.readLine.flatMap(name => Console.printLine(s"Your name is: ${name}"))
/**
* EXERCISE
*
* Implement a generic "zipRight" that sequentially composes the two effects
* using `flatMap`, but which succeeds with the success value of the effect
* on the right-hand side.
*/
def myZipRight[R, E, A, B](
left: ZIO[R, E, A],
right: ZIO[R, E, B]
): ZIO[R, E, B] =
???
}
object ForComprehension extends ZIOAppDefault {
/**
* EXERCISE
*
* Rewrite the following program to use a `for` comprehension.
*/
val run =
Console
.printLine("What is your name?")
.flatMap(
_ => Console.readLine.flatMap(name => Console.printLine(s"Your name is: ${name}"))
)
}
object ForComprehensionBackward extends ZIOAppDefault {
val readInt = Console.readLine.flatMap(string => ZIO.attempt(string.toInt)).orDie
/**
* EXERCISE
*
* Rewrite the following program, which uses a `for` comprehension, to use
* explicit `flatMap` and `map` methods. Note: each line of the `for`
* comprehension will translate to a `flatMap`, except the final line,
* which will translate to a `map`.
*/
val run = {
for {
_ <- Console.printLine("How old are you?")
age <- readInt
_ <- if (age < 18) Console.printLine("You are a kid!")
else Console.printLine("You are all grown up!")
} yield ()
}
}
object NumberGuesser extends ZIOAppDefault {
def analyzeAnswer(random: Int, guess: String) =
if (random.toString == guess.trim) Console.printLine("You guessed correctly!")
else Console.printLine(s"You did not guess correctly. The answer was ${random}")
/**
* EXERCISE
*
* Choose a random number (using `Random.nextInt`), and then ask the user to guess
* the number (using `Console.readLine`), feeding their response to `analyzeAnswer`,
* above.
*/
val run =
???
}
object SingleSyncInterop extends ZIOAppDefault {
/**
* EXERCISE
*
* Using ZIO.attempt, convert `println` into a ZIO function.
*/
def myPrintLn(line: String): Task[Unit] = ???
val run =
myPrintLn("Hello World!")
}
object MultipleSyncInterop extends ZIOAppDefault {
/**
* Using `ZIO.attempt`, wrap Scala's `println` method to lazily convert it
* into a functional effect, which describes the action of printing a line
* of text to the console, but which does not actually perform the print.
*/
def printLine(line: String): Task[Unit] = ???
/**
* Using `ZIO.attempt`, wrap Scala's `scala.io.StdIn.readLine()` method to
* lazily convert it into a ZIO effect, which describes the action of
* printing a line of text to the console, but which does not actually
* perform the print.
*/
val readLine: Task[String] = ???
val run = {
for {
_ <- printLine("Hello, what is your name?")
name <- readLine
_ <- printLine(s"Good to meet you, ${name}!")
} yield ()
}
}
object AsyncExample extends ZIOAppDefault {
import scala.concurrent.ExecutionContext.global
def loadBodyAsync(onSuccess: String => Unit, onFailure: Throwable => Unit): Unit =
global.execute { () =>
if (scala.util.Random.nextDouble() < 0.01) onFailure(new java.io.IOException("Could not load body!"))
else onSuccess("Body of request")
}
/**
* EXERCISE
*
* Using `ZIO.async`, convert the above callback-based API into a
* nice clean ZIO effect.
*/
lazy val loadBodyAsyncZIO: ZIO[Any, Throwable, String] =
???
val run =
for {
body <- loadBodyAsyncZIO
_ <- Console.printLine(body)
} yield ()
}