Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Finish cats 0.7.2 update and change deps method #70

Merged
1 commit merged into from
Nov 2, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 10 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,8 @@ To tell `Fetch` how to get the data you want, you must implement the `DataSource

Data Sources take two type parameters:

<ol>
<li><code>Identity</code> is a type that has enough information to fetch the data. For a users data source, this would be a user's unique ID.</li>
<li><code>Result</code> is the type of data we want to fetch. For a users data source, this would the `User` type.</li>
</ol>
1. `Identity` is a type that has enough information to fetch the data. For a users data source, this would be a user's unique ID.
2. `Result` is the type of data we want to fetch. For a users data source, this would the `User` type.

```scala
import cats.data.NonEmptyList
Expand All @@ -56,7 +54,7 @@ We'll implement a dummy data source that can convert integers to strings. For co

```scala
import cats.data.NonEmptyList
import cats.std.list._
import cats.instances.list._
import fetch._

implicit object ToStringSource extends DataSource[Int, String]{
Expand All @@ -69,7 +67,7 @@ implicit object ToStringSource extends DataSource[Int, String]{
override def fetchMany(ids: NonEmptyList[Int]): Query[Map[Int, String]] = {
Query.sync({
println(s"[${Thread.currentThread.getId}] Many ToString $ids")
ids.unwrap.map(i => (i, i.toString)).toMap
ids.toList.map(i => (i, i.toString)).toMap
})
}
}
Expand Down Expand Up @@ -99,7 +97,7 @@ Let's run it and wait for the fetch to complete:

```scala
fetchOne.runA[Id]
// [46] One ToString 1
// [45] One ToString 1
// res3: cats.Id[String] = 1
```

Expand All @@ -117,7 +115,7 @@ When executing the above fetch, note how the three identities get batched and th

```scala
fetchThree.runA[Id]
// [46] Many ToString OneAnd(1,List(2, 3))
// [45] Many ToString NonEmptyList(1, 2, 3)
// res5: cats.Id[(String, String, String)] = (1,2,3)
```

Expand All @@ -138,7 +136,7 @@ implicit object LengthSource extends DataSource[String, Int]{
override def fetchMany(ids: NonEmptyList[String]): Query[Map[String, Int]] = {
Query.async((ok, fail) => {
println(s"[${Thread.currentThread.getId}] Many Length $ids")
ok(ids.unwrap.map(i => (i, i.size)).toMap)
ok(ids.toList.map(i => (i, i.size)).toMap)
})
}
}
Expand All @@ -156,8 +154,8 @@ Note how the two independent data fetches run in parallel, minimizing the latenc

```scala
fetchMulti.runA[Id]
// [46] One ToString 1
// [47] One Length one
// [45] One ToString 1
// [46] One Length one
// res7: cats.Id[(String, Int)] = (1,3)
```

Expand All @@ -176,6 +174,6 @@ While running it, notice that the data source is only queried once. The next tim

```scala
fetchTwice.runA[Id]
// [46] One ToString 1
// [45] One ToString 1
// res8: cats.Id[(String, String)] = (1,1)
```
4 changes: 2 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ lazy val commonSettings = Seq(
resolvers += Resolver.sonatypeRepo("releases"),
libraryDependencies ++= Seq(
"org.typelevel" %%% "cats" % "0.7.2",
"org.scalatest" %%% "scalatest" % "3.0.0-M7" % "test",
"org.scalatest" %%% "scalatest" % "3.0.0" % "test",
compilerPlugin(
"org.spire-math" %% "kind-projector" % "0.7.1"
)
Expand Down Expand Up @@ -129,7 +129,7 @@ lazy val readme = (project in file("tut"))

lazy val monixSettings = (
libraryDependencies ++= Seq(
"io.monix" %%% "monix-eval" % "2.0"
"io.monix" %%% "monix-eval" % "2.0.5"
)
)

Expand Down
54 changes: 31 additions & 23 deletions docs/src/tut/docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ And now we're ready to write our user data source; we'll emulate a database with

```tut:silent
import cats.data.NonEmptyList
import cats.std.list._
import cats.instances.list._

import fetch._

Expand Down Expand Up @@ -461,7 +461,7 @@ combinator. It takes a `List[Fetch[A]]` and gives you back a `Fetch[List[A]]`, b
data source and running fetches to different sources in parallel. Note that the `sequence` combinator is more general and works not only on lists but on any type that has a [Traverse](http://typelevel.org/cats/tut/traverse.html) instance.

```tut:silent
import cats.std.list._
import cats.instances.list._
import cats.syntax.traverse._

val fetchSequence: Fetch[List[User]] = List(getUser(1), getUser(2), getUser(3)).sequence
Expand Down Expand Up @@ -587,22 +587,22 @@ go wrong:
- an identity may be missing
- the data source may be temporarily available

Since the error cases are plenty and can't be anticipated Fetch errors are represented by the `FetchError` trait, which extends `Throwable`.
Currently fetch defines `FetchError` cases for missing identities and arbitrary exceptions but you can extend `FetchError` with any error
Since the error cases are plenty and can't be anticipated Fetch errors are represented by the `FetchException` trait, which extends `Throwable`.
Currently fetch defines `FetchException` cases for missing identities and arbitrary exceptions but you can extend `FetchException` with any error
you want.

## Exceptions

What happens if we run a fetch and fails with an exception? We'll create a fetch that always fails to learn about it.

```tut:silent
val fetchError: Fetch[User] = (new Exception("Oh noes")).fetch
val fetchException: Fetch[User] = (new Exception("Oh noes")).fetch
```

If we try to execute to `Id` the exception will be thrown wrapped in a `FetchException`.

```tut:fail
fetchError.runA[Id]
fetchException.runA[Id]
```

Since `Id` runs the fetch eagerly, the only way to recover from errors when running it is surrounding it with a `try-catch` block. We'll use Cats' `Eval` type as the target
Expand All @@ -614,13 +614,13 @@ We can use the `FetchMonadError[Eval]#attempt` to convert a fetch result into a
import fetch.unsafe.implicits._
```

Now we can convert `Eval[User]` into `Eval[FetchError Xor User]` and capture exceptions as values in the left of the disjunction.
Now we can convert `Eval[User]` into `Eval[FetchException Xor User]` and capture exceptions as values in the left of the disjunction.

```tut:book
import cats.Eval
import cats.data.Xor

val safeResult: Eval[FetchError Xor User] = FetchMonadError[Eval].attempt(fetchError.runA[Eval])
val safeResult: Eval[FetchException Xor User] = FetchMonadError[Eval].attempt(fetchException.runA[Eval])

safeResult.value
```
Expand All @@ -630,13 +630,13 @@ And more succintly with Cats' applicative error syntax.
```tut:book
import cats.syntax.applicativeError._

fetchError.runA[Eval].attempt.value
fetchException.runA[Eval].attempt.value
```

## Missing identities

You've probably noticed that `DataSource.fetchOne` and `DataSource.fetchMany` return types help Fetch know if any requested
identity was not found. Whenever an identity cannot be found, the fetch execution will fail with an instance of `FetchError`.
identity was not found. Whenever an identity cannot be found, the fetch execution will fail with an instance of `FetchException`.

The requests can be of different types, each of which is described below.

Expand All @@ -647,9 +647,9 @@ should be able to easily diagnose the failure. For ilustrating this scenario we'

```tut:silent
val missingUser = getUser(5)
val eval: Eval[FetchError Xor User] = missingUser.runA[Eval].attempt
val result: FetchError Xor User = eval.value
val err: FetchError = result.swap.toOption.get // don't do this at home, folks
val eval: Eval[FetchException Xor User] = missingUser.runA[Eval].attempt
val result: FetchException Xor User = eval.value
val err: FetchException = result.swap.toOption.get // don't do this at home, folks
```

`NotFound` allows you to access the fetch request that was in progress when the error happened and the environment of the fetch.
Expand All @@ -673,9 +673,9 @@ When multiple requests to the same data source are batched and/or multiple reque

```tut:silent
val missingUsers = List(3, 4, 5, 6).traverse(getUser)
val eval: Eval[FetchError Xor List[User]] = missingUsers.runA[Eval].attempt
val result: FetchError Xor List[User] = eval.value
val err: FetchError = result.swap.toOption.get // don't do this at home, folks
val eval: Eval[FetchException Xor List[User]] = missingUsers.runA[Eval].attempt
val result: FetchException Xor List[User] = eval.value
val err: FetchException = result.swap.toOption.get // don't do this at home, folks
```

The `.missing` attribute will give us the mapping from data source name to missing identities, and `.env` will give us the environment so we can track the execution of the fetch.
Expand Down Expand Up @@ -974,7 +974,7 @@ Await.result(task.runAsync(ioSched), Duration.Inf)

## Custom types

If you want to run a fetch to a custom type `M[_]`, you need to implement the `FetchMonadError[M]` typeclass. `FetchMonadError[M]` is simply a `MonadError[M, FetchError]` from cats augmented
If you want to run a fetch to a custom type `M[_]`, you need to implement the `FetchMonadError[M]` typeclass. `FetchMonadError[M]` is simply a `MonadError[M, FetchException]` from cats augmented
with a method for running a `Query[A]` in the context of the monad `M[A]`.

For ilustrating integration with an asynchronous concurrency monad we'll use the implementation of Monix Task.
Expand Down Expand Up @@ -1045,28 +1045,36 @@ implicit val taskFetchMonadError: FetchMonadError[Task] = new FetchMonadError[Ta
override def product[A, B](fa: Task[A], fb: Task[B]): Task[(A, B)] =
Task.zip2(Task.fork(fa), Task.fork(fb)) // introduce parallelism with Task#fork

override def pureEval[A](e: Eval[A]): Task[A] = evalToTask(e)

def pure[A](x: A): Task[A] =
Task.now(x)

def handleErrorWith[A](fa: Task[A])(f: FetchError => Task[A]): Task[A] =
fa.onErrorHandleWith({ case e: FetchError => f(e) })
def handleErrorWith[A](fa: Task[A])(f: FetchException => Task[A]): Task[A] =
fa.onErrorHandleWith({ case e: FetchException => f(e) })

def raiseError[A](e: FetchError): Task[A] =
def raiseError[A](e: FetchException): Task[A] =
Task.raiseError(e)

def flatMap[A, B](fa: Task[A])(f: A => Task[B]): Task[B] =
fa.flatMap(f)

def tailRecM[A, B](a: A)(f: A => Task[Either[A, B]]): Task[B] =
defaultTailRecM(a)(f) // same implementation as monix.cats

override def runQuery[A](q: Query[A]): Task[A] = queryToTask(q)
}
```

```tut:silent
import cats.RecursiveTailRecM

val taskTR: RecursiveTailRecM[Task] =
RecursiveTailRecM.create[Task]
```

We can now import the above implicit and run a fetch to our custom type, let's give it a go:

```tut:book
val task = Fetch.run(homePage)(taskFetchMonadError)
val task = Fetch.run(homePage)(taskFetchMonadError, taskTR)

Await.result(task.runAsync(scheduler), Duration.Inf)
```
Expand Down
2 changes: 1 addition & 1 deletion docs/src/tut/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ We'll implement a dummy data source that can convert integers to strings. For co

```tut:silent
import cats.data.NonEmptyList
import cats.std.list._
import cats.instances.list._
import fetch._

implicit object ToStringSource extends DataSource[Int, String]{
Expand Down
3 changes: 2 additions & 1 deletion jvm/src/main/scala/unsafeImplicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ object implicits {
}
}
}
def pure[A](x: A): Eval[A] = Eval.now(x)
def pure[A](x: A): Eval[A] = Eval.now(x)

def tailRecM[A, B](a: A)(f: A => Eval[Either[A, B]]): Eval[B] = FM.tailRecM(a)(f)

def handleErrorWith[A](fa: Eval[A])(f: FetchException => Eval[A]): Eval[A] =
Expand Down
17 changes: 10 additions & 7 deletions monix/shared/src/main/scala/monix.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ package fetch.monixTask

import fetch._

import cats.{Eval, Now, Later, Always, Traverse, Applicative}
import cats.{Eval, Now, Later, Always, Monad, RecursiveTailRecM}

import monix.eval.Task
import monix.execution.{Scheduler, Cancelable}
Expand All @@ -29,33 +29,36 @@ object implicits {
def evalToTask[A](e: Eval[A]): Task[A] = e match {
case Now(x) => Task.now(x)
case l: Later[A] => Task.evalOnce({ l.value })
case a: Always[A] => Task.evalAlways({ a.value })
case a: Always[A] => Task.eval({ a.value })
case other => Task.evalOnce({ other.value })
}

implicit val fetchTaskRecursiveTailRecM: RecursiveTailRecM[Task] = RecursiveTailRecM.create[Task]

implicit val fetchTaskFetchMonadError: FetchMonadError[Task] = new FetchMonadError[Task] {
override def map[A, B](fa: Task[A])(f: A => B): Task[B] =
fa.map(f)

override def product[A, B](fa: Task[A], fb: Task[B]): Task[(A, B)] =
Task.zip2(Task.fork(fa), Task.fork(fb))

override def pureEval[A](e: Eval[A]): Task[A] = evalToTask(e)

def pure[A](x: A): Task[A] =
Task.now(x)

def handleErrorWith[A](fa: Task[A])(f: FetchError => Task[A]): Task[A] =
def handleErrorWith[A](fa: Task[A])(f: FetchException => Task[A]): Task[A] =
fa.onErrorHandleWith({
case e: FetchError => f(e)
case e: FetchException => f(e)
})

def raiseError[A](e: FetchError): Task[A] =
def raiseError[A](e: FetchException): Task[A] =
Task.raiseError(e)

def flatMap[A, B](fa: Task[A])(f: A => Task[B]): Task[B] =
fa.flatMap(f)

def tailRecM[A, B](a: A)(f: A => Task[Either[A, B]]): Task[B] =
defaultTailRecM(a)(f)

override def runQuery[A](q: Query[A]): Task[A] = q match {
case Sync(x) => evalToTask(x)
case Async(ac, timeout) => {
Expand Down
5 changes: 2 additions & 3 deletions monix/shared/src/test/scala/FetchTaskTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,15 @@ import monix.execution.Scheduler
import org.scalatest._

import cats.data.NonEmptyList
import cats.std.list._
import cats.instances.list._

import fetch._
import fetch.monixTask.implicits._

import scala.concurrent.Future

class FetchTaskTests extends AsyncFreeSpec with Matchers {
implicit def executionContext = Scheduler.Implicits.global
override def newInstance = new FetchTaskTests
implicit override def executionContext = Scheduler.Implicits.global

case class ArticleId(id: Int)
case class Article(id: Int, content: String) {
Expand Down
2 changes: 1 addition & 1 deletion project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.8")
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.13")
addSbtPlugin("de.heikoseeberger" % "sbt-header" % "1.5.1")
addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.0.0")
addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.5.4" exclude("com.typesafe.sbt", "sbt-git"))
Expand Down
Loading