Skip to content
Permalink
Browse files

arf-update_exercises (#5)

* Update Usage, Caching sections
Fix Comments
Create a batching section
* wip update cats errorhandling sections
wip create a debugging section

* reformat exercises update all content
wip update test

* update test
fix test and exercises

* revert ProjectPlugin

* fix and update test and exercises

* Update README.md

* reformat code update links

* reformat code

* change exercises batching section

* fix pr changes

* fix pr changes style and comment

* fix pr changes yield style
  • Loading branch information
AdrianRaFo authored and juanpedromoreno committed Apr 19, 2017
1 parent 25554d2 commit baba647e93ab195c74982073dc64d02695a14e5f
@@ -4,7 +4,7 @@

This repository hosts a content library for the [Scala Exercises](https://www.scala-exercises.org/) platform, including interactive exercises related to the [Fetch](https://github.com/47deg/fetch) data access library.

## About Scala exercises
## About Scala exercises

"Scala Exercises" brings exercises for the Stdlib, Cats, Shapeless and many other great libraries for Scala to your browser. Offering hundreds of solvable exercises organized into several categories covering the basics of the Scala language and it's most important libraries.

@@ -32,4 +32,4 @@ 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.
limitations under the License.
@@ -10,6 +10,7 @@ lazy val fetch = (project in file("."))
dep("exercise-compiler"),
dep("definitions"),
%%("fetch"),
%%("fetch-debug"),
%%("fetch-monix"),
%%("shapeless"),
%%("scalatest"),
@@ -0,0 +1,108 @@
/*
* scala-exercises - exercises-fetch
* Copyright (C) 2015-2016 47 Degrees, LLC. <http://www.47deg.com>
*/

package fetchlib

import cats._
import cats.instances.list._
import cats.syntax.traverse._
import fetch._
import fetch.syntax._
import fetch.unsafe.implicits._
import org.scalaexercises.definitions.Section
import org.scalatest.{Assertion, FlatSpec, Matchers}

/**
* = Batching =
*
* As we have learned, Fetch performs batched requests whenever it can.
* It also exposes a couple knobs for tweaking the maximum batch size
* and whether multiple batches are run in parallel or sequentially.
*
* @param name batching
*/
object BatchingSection extends FlatSpec with Matchers with Section {

import FetchTutorialHelper._

/**
*
* = Maximum batch size =
* When implementing a `DataSource`, there is a method we can override called `maxBatchSize`.
* When implementing it we can specify the maximum size of the batched requests to this data source,
* let’s try it out:
*
* {{{+
*
* implicit object BatchedUserSource extends DataSource[UserId, User]{
* override def name = "BatchedUser"
*
* override def maxBatchSize: Option[Int] = Some(2)
*
* override def fetchOne(id: UserId): Query[Option[User]] = {
* Query.sync({
* latency(userDatabase.get(id), s"One User $id")
* })
* }
* override def fetchMany(ids: NonEmptyList[UserId]): Query[Map[UserId, User]] = {
* Query.sync({
* latency(userDatabase.filterKeys(ids.toList.contains), s"Many Users $ids")
* })
* }
* }
*
* def getBatchedUser(id: Int): Fetch[User] = Fetch(id)(BatchedUserSource)
*
* }}}
*
* We have defined the maximum batch size to be 2,
* let’s see what happens when running a fetch that needs more than two users:
*/
def maximumSize(res0: Int) = {
val fetchManyBatchedUsers: Fetch[List[User]] = List(1, 2, 3, 4).traverse(getBatchedUser)
fetchManyBatchedUsers.runE[Id].rounds.size shouldBe res0
}

/**
* Batch execution strategy
* In the presence of multiple concurrent batches,
* we can choose between a sequential or parallel execution strategy.
* By default they will be run in parallel,
* but you can tweak it by overriding `DataSource#batchExection`.
*
* {{{
*
* implicit object SequentialUserSource extends DataSource[UserId, User]{
* override def name = "SequentialUser"
*
* override def maxBatchSize: Option[Int] = Some(2)
*
* override def batchExecution: ExecutionType = Sequential
*
* override def fetchOne(id: UserId): Query[Option[User]] = {
* Query.sync({
* latency(userDatabase.get(id), s"One User $id")
* })
* }
* override def fetchMany(ids: NonEmptyList[UserId]): Query[Map[UserId, User]] = {
* Query.sync({
* latency(userDatabase.filterKeys(ids.toList.contains), s"Many Users $ids")
* })
* }
* }
*
* def getSequentialUser(id: Int): Fetch[User] = Fetch(id)(SequentialUserSource)
*
* }}}
*
* We have defined the maximum batch size to be 2 and the batch execution to be sequential,
* let’s see what happens when running a fetch that needs more than one batch:
*
*/
def executionStrategy(res0: Int) = {
val fetchManySeqBatchedUsers: Fetch[List[User]] = List(1, 2, 3, 4).traverse(getSequentialUser)
fetchManySeqBatchedUsers.runE[Id].rounds.size shouldBe res0
}
}
@@ -5,18 +5,14 @@

package fetchlib

import cats.data.NonEmptyList
import org.scalatest._
import fetch._

import cats._
import fetch.unsafe.implicits._
import fetch.syntax._
import cats.instances.list._
import cats.syntax.cartesian._
import cats.syntax.traverse._

import org.scalaexercises.definitions._
import fetch._
import fetch.syntax._
import fetch.unsafe.implicits._
import org.scalaexercises.definitions.Section
import org.scalatest.{FlatSpec, Matchers}

/**
* = Caching =
@@ -36,67 +32,66 @@ object CachingSection extends FlatSpec with Matchers with Section {
*
* We'll be using the default in-memory cache, prepopulated with some data. The cache key of an identity
* is calculated with the `DataSource`'s `identity` method.
* {{{
* val cache = InMemoryCache(UserSource.identity(1) -> User(1, "@dialelo"))
* }}}
* We can pass a cache as argument when running a fetch
*
* We can pass a cache as the second argument when running a fetch with `Fetch.run`.
*
*/
def prepopulating(res0: Int) = {
val env = getUser(1).runE[Id](cache)
env.rounds.size should be(res0)
def prepopulating(res0: Int, res1: String) = {
val fetchUser: Fetch[User] = getUser(1)
val cache = InMemoryCache(UserSource.identity(1) -> User(1, "@one"))
Fetch.run[Id](fetchUser, cache) shouldBe User(res0, res1)
}

/**
* As you can see, when all the data is cached, no query to the data sources is executed since the results are available
* in the cache. We'll write a `totalFetched` function for computing the number of identities that were requested during
* a series of rounds:
*
* And as the first when using fetch syntax:
* {{{
* def totalFetched(rounds: Seq[Round]): Int =
* rounds.map((round: Round) => requestFetches(round.request)).toList.sum
*
* def requestFetches(r: FetchRequest): Int =
* r match {
* case FetchOne(_, _) => 1
* case FetchMany(ids, _) => ids.toList.size
* case Concurrent(requests) => requests.toList.map(requestFetches).sum
* }
*fetchUser.runA[Id](cache)
* res: cats.Id[User] = User(1,@one)
* }}}
* As you can see, when all the data is cached, no query to the data sources is executed since the results are
*
* available in the cache.
*
* If only part of the data is cached, the cached data won't be asked for:
*
*/
def cachePartialHits(res0: Int) = {
val env = List(1, 2, 3).traverse(getUser).runE[Id](cache)
totalFetched(env.rounds) should be(res0)
def cachePartialHits(res0: String, res1: String) = {

val fetchUser: Fetch[User] = getUser(1)
val cache = InMemoryCache(UserSource.identity(1) -> User(1, "@dialelo"))
val fetchManyUsers: Fetch[List[User]] = List(1, 2, 3).traverse(getUser)
fetchManyUsers.runA[Id].head.username shouldBe res0
Fetch.run[Id](fetchUser, cache).username shouldBe res1
}

/**
* = Replaying a fetch without querying any data source =
*
* When running a fetch, we are generally interested in its final result. However, we also have access to the cache
* and information about the executed rounds once we run a fetch. Fetch's interpreter keeps its state in an environment
* (implementing the `Env` trait), and we can get both the environment and result after running a fetch using `Fetch.runFetch`
* instead of `Fetch.run` or `value.runF` via it's implicit syntax `value.runE`.
* When running a fetch, we are generally interested in its final result.
* However, we also have access to the cache and information about the executed rounds once we run a fetch.
* Fetch’s interpreter keeps its state in an environment (implementing the `Env` trait),
* and we can get both the environment and result after running a fetch using `Fetch.runFetch` instead of `Fetch.run`
* or `value.runF` via it’s implicit syntax.
*
* 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
* data sources.
* 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 data sources.
*
*/
def replaying(res0: Int, res1: Int) = {
def fetchUsers = List(1, 2, 3).traverse(getUser)
val firstEnv = fetchUsers.runE[Id]

firstEnv.rounds.size should be(res0)
val firstEnv = fetchUsers.runE[Id]

firstEnv.rounds.size shouldBe res0

val secondEnv = fetchUsers.runE[Id](firstEnv.cache)

secondEnv.rounds.size should be(res1)
secondEnv.rounds.size shouldBe res1
}

/**
*
* ## Implementing a custom cache
* = Implementing a custom cache =
*
* The default cache is implemented as an immutable in-memory map,
* but users are free to use their own caches when running a fetch.
@@ -108,21 +103,21 @@ object CachingSection extends FlatSpec with Matchers with Section {
* Note that the `update` method in the `DataSourceCache` trait
* yields a new, updated cache.
*
* ```scala
* {{{
* trait DataSourceCache {
* def update[A](k: DataSourceIdentity, v: A): DataSourceCache
* def get[A](k: DataSourceIdentity): Option[A]
* }
* ```
* }}}
*
* Let's implement a cache that forgets everything we store in it.
*
* ```tut:silent
* {{{
* final case class ForgetfulCache() extends DataSourceCache {
* override def get[A](k: DataSourceIdentity): Option[A] = None
* override def update[A](k: DataSourceIdentity, v: A): ForgetfulCache = this
* }
* ```
* }}}
*
* We can now use our implementation of the cache when running a fetch.
*/
@@ -132,10 +127,8 @@ object CachingSection extends FlatSpec with Matchers with Section {
one <- getUser(1)
another <- getUser(1)
} yield (one, another)

val env = fetchSameTwice.runE[Id](ForgetfulCache())

env.rounds.size should be(res0)
env.rounds.size shouldBe res0
}

}
@@ -5,19 +5,16 @@

package fetchlib

import cats.data.NonEmptyList
import org.scalatest._
import fetch._

import cats._
import fetch.unsafe.implicits._
import cats.syntax.cartesian._
import fetch._
import fetch.syntax._
import scala.util.Try

import org.scalaexercises.definitions._
import fetch.unsafe.implicits._
import org.scalaexercises.definitions.Section
import org.scalatest.{FlatSpec, Matchers}

/**
* = cats =
* = Cats =
*
* Fetch is built using Cats' Free monad construction and thus works out of the box with
* cats syntax. Using Cats' syntax, we can make fetch declarations more concise, without
@@ -34,40 +31,47 @@ import org.scalaexercises.definitions._
*/
object CatsSection extends FlatSpec with Matchers with Section {

import FetchTutorialHelper._

/**
* = Applicative =
*
* The `|@|` operator (cartesian builder) allows us to combine multiple independent fetches, even when they
* are from different types, and apply a pure function to their results. We can use it
* as a more powerful alternative to the `product` method or `Fetch#join`:
*
* ```tut:silent
* ```
*
* Notice how the queries to posts are batched.
*
* ```tut:book
* fetchThree.runA[Id]
* ```
* {{{
* import cats.syntax.cartesian._
*
* val fetchThree: Fetch[(Post, User, Post)] = (getPost(1) |@| getUser(2) |@| getPost(2)).tupled
*
* fetchThree.runA[Id]
* // res: (Post(1,2,An article),User(2,@two),Post(2,3,Another article))
* }}}
*
* More interestingly, we can use it to apply a pure function to the results of various fetches.
*/
def applicative(res0: Int) = {
import cats.syntax.cartesian._

val ops =
(1.fetch |@| 2.fetch).map((a, b) => a + b)
def applicative(res0: String) = {
val fetchFriends: Fetch[String] = (getUser(1) |@| getUser(2)).map((one, other) =>
s"${one.username} is friends with ${other.username}")

ops.runA[Id] should be(res0)
fetchFriends.runA[Id] shouldBe res0
}

/**
* The above example is equivalent to the following using the `Fetch#join` method:
*/
def similarToJoin(res0: Int) = {
val ops =
Fetch.join(1.fetch, 2.fetch).map { case (a, b) => a + b }
def similarToJoin(res0: String) = {
val fetchLoves: Fetch[String] = Fetch
.join(getUser(1), getUser(2))
.map({
case (one, other) =>
s"${one.username} loves ${other.username}"
})

ops.runA[Id] should be(res0)
fetchLoves.runA[Id] shouldBe res0
}

}

0 comments on commit baba647

Please sign in to comment.
You can’t perform that action at this time.