Skip to content

Commit 339a162

Browse files
author
committed
Merge pull request #33 from 47deg/rr-fetch-syntax
Fetch Syntax
2 parents 7c75958 + b9745f5 commit 339a162

6 files changed

Lines changed: 326 additions & 130 deletions

File tree

docs/src/tut/docs.md

Lines changed: 121 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,8 @@ When we are creating and combining `Fetch` values, we are just constructing a re
118118
dependencies.
119119

120120
```tut:silent
121-
import cats.Id
122121
import fetch.implicits._
122+
import fetch.syntax._
123123
124124
val fetchUser: Fetch[User] = getUser(1)
125125
```
@@ -137,14 +137,14 @@ Note that Fetch provides `MonadError` instances for a variety of different monad
137137
Let's run our first fetch!
138138

139139
```tut:book
140-
val result: User = Fetch.run[Id](fetchUser)
140+
val result: User = fetchUser.runA[Eval].value
141141
```
142142

143143
In the previous examples, we:
144144

145-
- brought the implicit instance of `MonadError[Id, Throwable]` into scope importing `fetch.implicits._`
145+
- brought the implicit instance of `MonadError[Eval, Throwable]` into scope importing `fetch.implicits._`
146146
- created a fetch for a `User` using the `getUser` function
147-
- interpreted the fetch to a `Id[User]` (which is just a `User`) using `Fetch.run`
147+
- interpreted the fetch to a `Eval[User]` using the syntax `runA` that delegate to `Fetch.run`
148148

149149
As you can see, the fetch was executed in one round to fetch the user and was finished after that.
150150

@@ -162,7 +162,7 @@ val fetchTwoUsers: Fetch[(User, User)] = for {
162162
When composing fetches with `flatMap` we are telling Fetch that the second one depends on the previous one, so it isn't able to make any optimizations. When running the above fetch, we will query the user data source in two rounds: one for the user with id 1 and another for the user with id 2.
163163

164164
```tut:book
165-
val result: (User, User) = Fetch.run[Id](fetchTwoUsers)
165+
val result: (User, User) = fetchTwoUsers.runA[Eval].value
166166
```
167167

168168
### Batching
@@ -180,7 +180,7 @@ val fetchProduct: Fetch[(User, User)] = getUser(1).product(getUser(2))
180180
Note how both ids (1 and 2) are requested in a single query to the data source when executing the fetch.
181181

182182
```tut:book
183-
val result: (User, User) = Fetch.run[Id](fetchProduct)
183+
val result: (User, User) = fetchProduct.runA[Eval].value
184184
```
185185

186186
### Deduplication
@@ -194,7 +194,7 @@ val fetchDuped: Fetch[(User, User)] = getUser(1).product(getUser(1))
194194
Note that when running the fetch, the identity 1 is only requested once even when it is needed by both fetches.
195195

196196
```tut:book
197-
val result: (User, User) = Fetch.run[Id](fetchDuped)
197+
val result: (User, User) = fetchDuped.runA[Eval].value
198198
```
199199

200200
### Caching
@@ -214,7 +214,7 @@ val fetchCached: Fetch[(User, User)] = for {
214214
The above fetch asks for the same identity multiple times. Let's see what happens when executing it.
215215

216216
```tut:book
217-
val result: (User, User) = Fetch.run[Id](fetchCached)
217+
val result: (User, User) = fetchCached.runA[Eval].value
218218
```
219219

220220
As you can see, the `User` with id 1 was fetched only once in a single round-trip. The next
@@ -257,9 +257,9 @@ implicit object PostSource extends DataSource[PostId, Post]{
257257
def getPost(id: PostId): Fetch[Post] = Fetch(id)
258258
259259
val postInfoDatabase: Map[PostId, PostInfo] = Map(
260-
1 -> PostInfo("monad"),
261-
2 -> PostInfo("applicative"),
262-
3 -> PostInfo("monad")
260+
1 -> PostInfo("Run Wild, Run Free"),
261+
2 -> PostInfo("American Psycho"),
262+
3 -> PostInfo("Torrente 3")
263263
)
264264
265265
implicit object PostInfoSource extends DataSource[PostId, PostInfo]{
@@ -292,7 +292,7 @@ val fetchMulti: Fetch[(Post, User)] = for {
292292
We can now run the previous fetch, querying the posts data source first and the user data source afterwards.
293293

294294
```tut:book
295-
val result: (Post, User) = Fetch.run[Id](fetchMulti)
295+
val result: (Post, User) = fetchMulti.runA[Eval].value
296296
```
297297

298298
In the previous example, we fetched a post given its id and then fetched its author. This
@@ -316,7 +316,7 @@ val fetchConcurrent: Fetch[(Post, User)] = getPost(1).product(getUser(2))
316316
The above example combines data from two different sources, and the library knows they are independent.
317317

318318
```tut:book
319-
val result: (Post, User) = Fetch.run[Id](fetchConcurrent)
319+
val result: (Post, User) = fetchConcurrent.runA[Eval].value
320320
```
321321

322322
Since we are interpreting the fetch to the `Id` monad, that doesn't give us any parallelism; the fetches
@@ -344,7 +344,7 @@ val fetchSequence: Fetch[List[User]] = List(getUser(1), getUser(2), getUser(3)).
344344
Since `sequence` uses applicative operations internally, the library is able to perform optimizations across all the sequenced fetches.
345345

346346
```tut:book
347-
val result: List[User] = Fetch.run[Id](fetchSequence)
347+
val result: List[User] = fetchSequence.runA[Eval].value
348348
```
349349

350350
As you can see, requests to the user data source were batched, thus fetching all the data in one round.
@@ -360,7 +360,7 @@ val fetchTraverse: Fetch[List[User]] = List(1, 2, 3).traverse(getUser)
360360
As you may have guessed, all the optimizations made by `sequence` still apply when using `traverse`.
361361

362362
```tut:book
363-
val result: List[User] = Fetch.run[Id](fetchTraverse)
363+
val result: List[User] = fetchTraverse.runA[Eval].value
364364
```
365365

366366
# Interpreting a fetch to an async capable monad
@@ -390,12 +390,11 @@ val fetchParallel: Fetch[(User, Post)] = (getUser(1) |@| getPost(1)).tupled
390390
We can now interpret a fetch into a future:
391391

392392
```tut:book
393-
val fut: Future[(User, Post)] = Fetch.run[Future](fetchParallel)
393+
val fut: Future[(User, Post)] = fetchParallel.runA[Future]
394394
Await.result(fut, 1 seconds) // this call blocks the current thread, don't do this at home!
395395
```
396396

397-
Since futures run in a thread pool, we need to explicitly set println output to the standard output. Note how both requests
398-
to the data sources run in parallel, each in its own logical thread.
397+
Since futures run in a thread pool, both requests to the data sources run in parallel, each in its own logical thread.
399398

400399
# Caching
401400

@@ -415,7 +414,7 @@ val cache = InMemoryCache(UserSource.identity(1) -> User(1, "@dialelo"))
415414
We can pass a cache as the second argument when running a fetch with `Fetch.run`.
416415

417416
```tut:book
418-
val result: User = Fetch.run[Id](fetchUser, cache)
417+
val result: User = fetchUser.runA[Eval](cache).value
419418
```
420419

421420
As you can see, when all the data is cached, no query to the data sources is executed since the results are available
@@ -428,23 +427,23 @@ val fetchManyUsers: Fetch[List[User]] = List(1, 2, 3).traverse(getUser)
428427
If only part of the data is cached, the cached data won't be asked for:
429428

430429
```tut:book
431-
val result: List[User] = Fetch.run[Id](fetchManyUsers, cache)
430+
val result: List[User] = fetchManyUsers.runA[Eval](cache).value
432431
```
433432

434433
## Replaying a fetch without querying any data source
435434

436435
When running a fetch, we are generally interested in its final result. However, we also have access to the cache
437436
and information about the executed rounds once we run a fetch. Fetch's interpreter keeps its state in an environment
438437
(implementing the `Env` trait), and we can get both the environment and result after running a fetch using `Fetch.runFetch`
439-
instead of `Fetch.run`.
438+
instead of `Fetch.run` or `value.runF` via it's implicit syntax.
440439

441440
Knowing this, we can replay a fetch reusing the cache of a previous one. The replayed fetch won't have to call any of the
442441
data sources.
443442

444443
```tut:book
445-
val populatedCache = Fetch.runEnv[Id](fetchManyUsers).cache
444+
val populatedCache = fetchManyUsers.runE[Eval].value.cache
446445
447-
val result: List[User] = Fetch.run[Id](fetchManyUsers, populatedCache)
446+
val result: List[User] = fetchManyUsers.runA[Eval](populatedCache).value
448447
```
449448

450449
## Implementing a custom cache
@@ -481,7 +480,7 @@ val myCache = MyInMemoryCache(Map(UserSource.identity(1) -> User(1, "dialelo")))
481480
We can now use our implementation of the cache when running a fetch.
482481

483482
```tut:book
484-
val result: User = Fetch.run[Id](fetchUser, myCache)
483+
val result: User = fetchUser.runA[Eval](myCache).value
485484
```
486485

487486
# Error handling
@@ -494,17 +493,19 @@ One of the most interesting combinators is `attempt`, which given a `M[A]` yield
494493
in the `Eval` monad to an `Xor` and not worry about exceptions. Let's create a fetch that always fails when executed:
495494

496495
```tut:silent
497-
import cats.data.Xor
498-
import fetch.implicits.evalMonadError
499-
500-
val fetchError: Fetch[User] = Fetch.error(new Exception("Oh noes"))
496+
val fetchError: Fetch[User] = (new Exception("Oh noes")).fetch
501497
```
502498

503499
We can now use the Eval MonadError's `attempt` to convert a fetch result into a disjuntion and avoid throwing exceptions.
504500

505501
```tut:book
506-
val result: Eval[User] = Fetch.run[Eval](fetchError)
507-
val safeResult: Eval[Throwable Xor User] = evalMonadError.attempt(result)
502+
import cats.data.Xor
503+
import cats.MonadError
504+
505+
val ME = implicitly[MonadError[Eval, Throwable]]
506+
507+
val result: Eval[User] = fetchError.runA[Eval]
508+
val safeResult: Eval[Throwable Xor User] = ME.attempt(result)
508509
val finalValue: Throwable Xor User = safeResult.value
509510
```
510511

@@ -522,9 +523,82 @@ about the execution of the fetch.
522523

523524
# Syntax
524525

526+
## Implicit syntax
527+
528+
Fetch provides implicit syntax to lift any value to the context of a `Fetch` in addition to the most common used
529+
combinators active within `Fetch` instances.
530+
531+
### pure
532+
533+
Plain values can be lifted to the Fetch monad with `value.fetch`:
534+
535+
```tut:silent
536+
val fetchPure: Fetch[Int] = 42.fetch
537+
```
538+
539+
Executing a pure fetch doesn't query any data source, as expected.
540+
541+
```tut:book
542+
val result: Int = fetchPure.runA[Eval].value
543+
```
544+
545+
### error
546+
547+
Errors can also be lifted to the Fetch monad via `exception.fetch`. Note that interpreting
548+
an errorful fetch to `Eval` won't throw the exception unless we access the value with the `.value` method.
549+
550+
A safer way to deal with errors is to use MonadError's `attempt` to turn the exception into a `Xor.Left` value:
551+
552+
```tut:silent
553+
val ME = implicitly[MonadError[Eval, Throwable]]
554+
555+
val fetchFail: Fetch[Int] = (new Exception("Something went terribly wrong")).fetch[Int]
556+
val result: Eval[Int] = fetchFail.runA[Eval]
557+
val safeResult: Eval[Throwable Xor Int] = ME.attempt(result)
558+
val finalValue: Throwable Xor Int = safeResult.value
559+
```
560+
561+
### join
562+
563+
We can compose two independent fetches with `fetch1.join(fetch2)`.
564+
565+
```tut:silent
566+
val fetchJoined: Fetch[(Post, User)] = getPost(1).join(getUser(2))
567+
```
568+
569+
If the fetches are to the same data source they will be batched; if they aren't, they will be evaluated at the same time.
570+
571+
```tut:book
572+
val result: (Post, User) = fetchJoined.runA[Eval].value
573+
```
574+
575+
### runA
576+
577+
Run directly any fetch to a target any target `Monad` with a `MonadError` instance in scope `fetch1.runA[Eval]`.
578+
579+
```tut:silent
580+
val post: Eval[Post] = getPost(1).runA[Eval]
581+
```
582+
583+
### runE
584+
585+
Extract a fetch an get it's runtime environment `fetch1.runE[Eval]`.
586+
587+
```tut:silent
588+
val env: Eval[FetchEnv] = getPost(1).runE[Eval]
589+
```
590+
591+
### runF
592+
593+
Run a fetch obtaining the environment and final value `fetch1.runF[Eval]`.
594+
595+
```tut:silent
596+
val env: Eval[(FetchEnv, Post)] = getPost(1).runF[Eval]
597+
```
598+
525599
## Companion object
526600

527-
We've been using cats' syntax throughout the examples since it's more concise and general than the
601+
We've been using `cats.syntax' and `fetch.syntax` throughout the examples since it's more concise and general than the
528602
methods in the `Fetch` companion object. However, you can use the methods in the companion object
529603
directly.
530604

@@ -541,16 +615,23 @@ val fetchPure: Fetch[Int] = Fetch.pure(42)
541615
Executing a pure fetch doesn't query any data source, as expected.
542616

543617
```tut:book
544-
val result: Int = Fetch.run[Id](fetchPure)
618+
val result: Int = Fetch.run[Eval](fetchPure).value
545619
```
546620

547621
### error
548622

549623
Errors can also be lifted to the Fetch monad, in this case with `Fetch#error`. Note that interpreting
550-
an errorful fetch to `Id` will throw the exception so we won't do that:
624+
an errorful fetch to `Eval` won't throw the exception unless we access the value with the `.value` method.
625+
626+
A safer way to deal with errors is to use MonadError's `attempt` to turn the exception into a `Xor.Left` value:
627+
628+
```tut:book
629+
val ME = implicitly[MonadError[Eval, Throwable]]
551630
552-
```tut:silent
553631
val fetchFail: Fetch[Int] = Fetch.error(new Exception("Something went terribly wrong"))
632+
val result: Eval[Int] = fetchFail.runA[Eval]
633+
val safeResult: Eval[Throwable Xor Int] = ME.attempt(result)
634+
val finalValue: Throwable Xor Int = safeResult.value
554635
```
555636

556637
### join
@@ -564,7 +645,7 @@ val fetchJoined: Fetch[(Post, User)] = Fetch.join(getPost(1), getUser(2))
564645
If the fetches are to the same data source they will be batched; if they aren't, they will be evaluated at the same time.
565646

566647
```tut:book
567-
val result: (Post, User) = Fetch.run[Id](fetchJoined)
648+
val result: (Post, User) = Fetch.run[Eval](fetchJoined).value
568649
```
569650

570651
### sequence
@@ -579,7 +660,7 @@ val fetchSequence: Fetch[List[User]] = Fetch.sequence(List(getUser(1), getUser(2
579660
Note that `Fetch#sequence` is not as general as the `sequence` method from `Traverse`, but performs the same optimizations.
580661

581662
```tut:book
582-
val result: List[User] = Fetch.run[Id](fetchSequence)
663+
val result: List[User] = Fetch.run[Eval](fetchSequence).value
583664
```
584665

585666
### traverse
@@ -593,7 +674,7 @@ val fetchTraverse: Fetch[List[User]] = Fetch.traverse(List(1, 2, 3))(getUser)
593674
Note that `Fetch#traverse` is not as general as the `traverse` method from `Traverse`, but performs the same optimizations.
594675

595676
```tut:book
596-
val result: List[User] = Fetch.run[Id](fetchTraverse)
677+
val result: List[User] = Fetch.run[Eval](fetchTraverse).value
597678
```
598679

599680
## cats
@@ -624,7 +705,7 @@ val fetchThree: Fetch[(Post, User, Post)] = (getPost(1) |@| getUser(2) |@| getPo
624705
Notice how the queries to posts are batched.
625706

626707
```tut:book
627-
val result: (Post, User, Post) = Fetch.run[Id](fetchThree)
708+
val result: (Post, User, Post) = fetchThree.runA[Eval].value
628709
```
629710

630711
More interestingly, we can use it to apply a pure function to the results of various
@@ -635,7 +716,7 @@ val fetchFriends: Fetch[String] = (getUser(1) |@| getUser(2)).map({ (one, other)
635716
s"${one.username} is friends with ${other.username}"
636717
})
637718
638-
val result: String = Fetch.run[Id](fetchFriends)
719+
val result: String = fetchFriends.runA[Eval].value
639720
```
640721

641722
The above example is equivalent to the following using the `Fetch#join` method:
@@ -645,7 +726,7 @@ val fetchFriends: Fetch[String] = Fetch.join(getUser(1), getUser(2)).map({ case
645726
s"${one.username} is friends with ${other.username}"
646727
})
647728
648-
val result: String = Fetch.run[Id](fetchFriends)
729+
val result: String = fetchFriends.runA[Eval].value
649730
```
650731

651732
# Resources

0 commit comments

Comments
 (0)