Skip to content

Commit

Permalink
Merge pull request #187 from 47deg/loosen-constraints
Browse files Browse the repository at this point in the history
Loosen implicit constraints
  • Loading branch information
purrgrammer authored Mar 15, 2019
2 parents 72eda02 + 9e4c9d3 commit 2dee74b
Show file tree
Hide file tree
Showing 8 changed files with 58 additions and 69 deletions.
22 changes: 12 additions & 10 deletions examples/src/test/scala/DoobieExample.scala
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ object DatabaseExample {

def fetchByIds(ids: NonEmptyList[AuthorId]): ConnectionIO[List[Author]] = {
val q = fr"SELECT * FROM author WHERE" ++ Fragments.in(fr"id", ids)
q.query[Author].list
q.query[Author].to[List]
}
}

Expand All @@ -64,38 +64,40 @@ object DatabaseExample {
case (name, id) => Author(id + 1, name)
}

def createTransactor[F[_]: ConcurrentEffect] =
H2Transactor[F]("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1", "sa", "")
def createTransactor[F[_]: Async] =
H2Transactor.newH2Transactor[F]("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1", "sa", "")

def transactor[F[_]: ConcurrentEffect]: F[Transactor[F]] =
def transactor[F[_]: Async]: F[Transactor[F]] =
for {
xa <- createTransactor
xa <- createTransactor[F]
_ <- (dropTable *> createTable *> authors.traverse(addAuthor)).transact(xa)
} yield xa
}

object Authors extends Data[AuthorId, Author] {
def name = "Authors"

def db[F[_]: ConcurrentEffect]: DataSource[F, AuthorId, Author] =
def db[F[_]: Concurrent]: DataSource[F, AuthorId, Author] =
new DataSource[F, AuthorId, Author] {
def data = Authors

override def CF = ConcurrentEffect[F]
override def CF = Concurrent[F]

override def fetch(id: AuthorId): F[Option[Author]] =
Database.transactor
Database
.transactor[F]
.flatMap(Queries.fetchById(id).transact(_))

override def batch(ids: NonEmptyList[AuthorId]): F[Map[AuthorId, Author]] =
Database.transactor
Database
.transactor[F]
.flatMap(Queries.fetchByIds(ids).transact(_))
.map { authors =>
authors.map(a => AuthorId(a.id) -> a).toMap
}
}

def fetchAuthor[F[_]: ConcurrentEffect](id: Int): Fetch[F, Author] =
def fetchAuthor[F[_]: Concurrent](id: Int): Fetch[F, Author] =
Fetch(AuthorId(id), Authors.db)
}
}
Expand Down
26 changes: 13 additions & 13 deletions examples/src/test/scala/GithubExample.scala
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class GithubExample extends WordSpec with Matchers {
// http4s client which is used by the datasources

def client[F[_]: ConcurrentEffect] =
Http1Client[F](BlazeClientConfig.defaultConfig)
BlazeClientBuilder[F](executionContext).resource

// -- repos

Expand Down Expand Up @@ -78,7 +78,7 @@ class GithubExample extends WordSpec with Matchers {
def data = Repos

def fetch(id: (String, String)): F[Option[Repo]] = {
client[F] >>= ((c) => {
client[F].use((c) => {
val (owner, repo) = id
val url = GITHUB / "repos" / owner / repo +? ("access_token", ACCESS_TOKEN)
val req = Request[F](Method.GET, url)
Expand Down Expand Up @@ -109,12 +109,12 @@ class GithubExample extends WordSpec with Matchers {
implicit val repoED: EntityDecoder[F, Repo] = jsonOf
implicit val reposED: EntityDecoder[F, List[Repo]] = jsonOf

def CF = ConcurrentEffect[F]
def CF = Concurrent[F]

def data = OrgRepos

def fetch(org: Org): F[Option[List[Repo]]] = {
client[F] >>= ((c) => {
client[F].use((c) => {
val url = GITHUB / "orgs" / org / "repos" +? ("access_token", ACCESS_TOKEN) +? ("type", "public") +? ("per_page", 100)
val req = Request[F](Method.GET, url)
fetchCollectionRecursively[F, Repo](c, req).map(Option(_))
Expand Down Expand Up @@ -145,7 +145,7 @@ class GithubExample extends WordSpec with Matchers {
def data = Languages

def fetch(repo: Repo): F[Option[List[Language]]] = {
client[F] >>= ((c) => {
client[F].use((c) => {
val url = Uri.unsafeFromString(repo.languages_url) +? ("access_token", ACCESS_TOKEN)
val req = Request[F](Method.GET, url)
fetchCollectionRecursively[F, Language](c, req).map(Option(_))
Expand Down Expand Up @@ -175,8 +175,9 @@ class GithubExample extends WordSpec with Matchers {
def data = Contributors

def fetch(repo: Repo): F[Option[List[Contributor]]] = {
client[F] >>= ((c) => {
val url = Uri.unsafeFromString(repo.contributors_url) +? ("access_token", ACCESS_TOKEN) +? ("type", "public") +? ("per_page", 100)
client[F].use((c) => {
val url = Uri
.unsafeFromString(repo.contributors_url) +? ("access_token", ACCESS_TOKEN) +? ("type", "public") +? ("per_page", 100)
val req = Request[F](Method.GET, url)
fetchCollectionRecursively[F, Contributor](c, req).map(Option(_))
})
Expand Down Expand Up @@ -211,7 +212,7 @@ class GithubExample extends WordSpec with Matchers {
fetchOrg(org).map(projects => projects.map(_.languages.toSet).fold(Set())(_ ++ _).size)

"We can fetch org repos" in {
val io = Fetch.runLog[IO](fetchOrg("47deg"))
val io = Fetch.runLog[IO](fetchOrg[IO]("47deg"))

val (log, result) = io.unsafeRunSync

Expand All @@ -223,7 +224,7 @@ class GithubExample extends WordSpec with Matchers {
val GITHUB: Uri = Uri.unsafeFromString("https://api.github.com")

private def fetchCollectionRecursively[F[_], A](c: Client[F], req: Request[F])(
implicit CF: ConcurrentEffect[F],
implicit CF: MonadError[F, Throwable],
E: EntityDecoder[F, List[A]]
): F[List[A]] = {
val REL_NEXT = "rel=\"next\"".r
Expand All @@ -239,17 +240,16 @@ class GithubExample extends WordSpec with Matchers {
REL_NEXT
.findFirstMatchIn(raw)
.fold(
Sync[F].raiseError[String](new Exception("Couldn't find next link"))
CF.raiseError[String](new Exception("Couldn't find next link"))
)(m => {
Sync[F].pure(
m.before.toString.split(",").last.trim.dropWhile(_ == '<').takeWhile(_ != '>'))
CF.pure(m.before.toString.split(",").last.trim.dropWhile(_ == '<').takeWhile(_ != '>'))
})
}

def getNext(res: Response[F]): F[Uri] =
res.headers
.get(CaseInsensitiveString("Link"))
.fold(Sync[F].raiseError[Uri](new Exception("next not found")))(
.fold(CF.raiseError[Uri](new Exception("next not found")))(
raw => getNextLink(raw.value).map(Uri.unsafeFromString(_))
)

Expand Down
1 change: 0 additions & 1 deletion examples/src/test/scala/MonixExample.scala
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ class MonixExample extends AsyncFreeSpec with Matchers {
implicit val scheduler: Scheduler = Scheduler.io(name = "test-scheduler")
override val executionContext: ExecutionContext = scheduler
implicit val t: Timer[Task] = scheduler.timer
implicit val cs: ContextShift[Task] = scheduler.contextShift

import DatabaseExample._

Expand Down
10 changes: 5 additions & 5 deletions shared/src/main/scala/datasource.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ package fetch

import cats.{Functor, Monad}
import cats.effect._
import cats.data.NonEmptyList
import cats.data.{NonEmptyList, NonEmptyMap}
import cats.instances.list._
import cats.instances.option._
import cats.syntax.all._
import cats.kernel.{ Hash => H }
import cats.kernel.{Hash => H}

/**
* `Data` is a trait used to identify and optimize access to a `DataSource`.
Expand All @@ -45,7 +45,7 @@ object Data {
trait DataSource[F[_], I, A] {
def data: Data[I, A]

implicit def CF: ConcurrentEffect[F]
implicit def CF: Concurrent[F]

/** Fetch one identity, returning a None if it wasn't found.
*/
Expand All @@ -56,8 +56,8 @@ trait DataSource[F[_], I, A] {
*/
def batch(ids: NonEmptyList[I]): F[Map[I, A]] =
FetchExecution.parallel(
ids.map(id => fetch(id).map((v) => id -> v))
).map(_.collect({ case (id, Some(x)) => id -> x }).toMap)
ids.map(id => fetch(id).tupleLeft(id))
).map(_.collect { case (id, Some(x)) => id -> x }.toMap)

def maxBatchSize: Option[Int] = None

Expand Down
2 changes: 1 addition & 1 deletion shared/src/main/scala/execution.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import cats.syntax.all._

private object FetchExecution {
def parallel[F[_], A](effects: NonEmptyList[F[A]])(
implicit CF: ConcurrentEffect[F]
implicit CF: Concurrent[F]
): F[NonEmptyList[A]] =
effects.traverse(CF.start(_)).flatMap(fibers =>
fibers.traverse(_.join).onError({ case _ => fibers.traverse_(_.cancel) })
Expand Down
58 changes: 23 additions & 35 deletions shared/src/main/scala/fetch.scala
Original file line number Diff line number Diff line change
Expand Up @@ -254,16 +254,16 @@ object `package` {
/**
* Lift a plain value to the Fetch monad.
*/
def pure[F[_]: ConcurrentEffect, A](a: A): Fetch[F, A] =
def pure[F[_]: Applicative, A](a: A): Fetch[F, A] =
Unfetch(Applicative[F].pure(Done(a)))

def exception[F[_]: ConcurrentEffect, A](e: Log => FetchException): Fetch[F, A] =
def exception[F[_]: Applicative, A](e: Log => FetchException): Fetch[F, A] =
Unfetch(Applicative[F].pure(Throw[F, A](e)))

def error[F[_]: ConcurrentEffect, A](e: Throwable): Fetch[F, A] =
exception((log) => UnhandledException(e, log))
def error[F[_]: Applicative, A](e: Throwable): Fetch[F, A] =
exception(log => UnhandledException(e, log))

def apply[F[_] : ConcurrentEffect, I, A](
def apply[F[_]: Concurrent, I, A](
id: I,
ds: DataSource[F, I, A]
): Fetch[F, A] =
Expand All @@ -278,14 +278,14 @@ object `package` {
} yield Blocked(blockedRequest, Unfetch[F, A](
deferred.get.map {
case FetchDone(a) =>
Done(a).asInstanceOf[FetchResult[F, A]]
Done(a.asInstanceOf[A])
case FetchMissing() =>
Throw((log) => MissingIdentity(id, request.asInstanceOf[FetchQuery[I, A]], log))
Throw(log => MissingIdentity(id, request, log))
}
))
)

def optional[F[_] : ConcurrentEffect, I, A](
def optional[F[_] : Concurrent, I, A](
id: I,
ds: DataSource[F, I, A]
): Fetch[F, Option[A]] =
Expand All @@ -300,7 +300,7 @@ object `package` {
} yield Blocked(blockedRequest, Unfetch[F, Option[A]](
deferred.get.map {
case FetchDone(a) =>
Done(Some(a)).asInstanceOf[FetchResult[F, Option[A]]]
Done(Some(a.asInstanceOf[A]))
case FetchMissing() =>
Done(Option.empty[A])
}
Expand All @@ -319,8 +319,7 @@ object `package` {
fa: Fetch[F, A]
)(
implicit
C: ConcurrentEffect[F],
CS: ContextShift[F],
C: Concurrent[F],
T: Timer[F]
): F[A] =
apply(fa, InMemoryCache.empty[F])
Expand All @@ -330,8 +329,7 @@ object `package` {
cache: DataCache[F]
)(
implicit
C: ConcurrentEffect[F],
CS: ContextShift[F],
C: Concurrent[F],
T: Timer[F]
): F[A] = for {
cache <- Ref.of[F, DataCache[F]](cache)
Expand All @@ -349,8 +347,7 @@ object `package` {
fa: Fetch[F, A]
)(
implicit
C: ConcurrentEffect[F],
CS: ContextShift[F],
C: Concurrent[F],
T: Timer[F]
): F[(Log, A)] =
apply(fa, InMemoryCache.empty[F])
Expand All @@ -360,8 +357,7 @@ object `package` {
cache: DataCache[F]
)(
implicit
C: ConcurrentEffect[F],
CS: ContextShift[F],
C: Concurrent[F],
T: Timer[F]
): F[(Log, A)] = for {
log <- Ref.of[F, Log](FetchLog())
Expand All @@ -381,8 +377,7 @@ object `package` {
fa: Fetch[F, A]
)(
implicit
C: ConcurrentEffect[F],
CS: ContextShift[F],
C: Concurrent[F],
T: Timer[F]
): F[(DataCache[F], A)] =
apply(fa, InMemoryCache.empty[F])
Expand All @@ -392,8 +387,7 @@ object `package` {
cache: DataCache[F]
)(
implicit
C: ConcurrentEffect[F],
CS: ContextShift[F],
C: Concurrent[F],
T: Timer[F]
): F[(DataCache[F], A)] = for {
cache <- Ref.of[F, DataCache[F]](cache)
Expand All @@ -410,8 +404,7 @@ object `package` {
log: Option[Ref[F, Log]]
)(
implicit
C: ConcurrentEffect[F],
CS: ContextShift[F],
C: Concurrent[F],
T: Timer[F]
): F[A] = for {
result <- fa.run
Expand All @@ -437,8 +430,7 @@ object `package` {
log: Option[Ref[F, Log]]
)(
implicit
C: ConcurrentEffect[F],
CS: ContextShift[F],
C: Concurrent[F],
T: Timer[F]
): F[Unit] = {
val blocked = rs.m.toList.map(_._2)
Expand All @@ -464,13 +456,12 @@ object `package` {
log: Option[Ref[F, Log]]
)(
implicit
C: ConcurrentEffect[F],
CS: ContextShift[F],
C: Concurrent[F],
T: Timer[F]
): F[List[Request]] =
blocked.request match {
case q @ FetchOne(id, _) => runFetchOne[F](q, ds, blocked.result, cache, log)
case q @ Batch(ids, _) => runBatch[F](q, ds, blocked.result, cache, log)
case q @ FetchOne(_, _) => runFetchOne[F](q, ds, blocked.result, cache, log)
case q @ Batch(_, _) => runBatch[F](q, ds, blocked.result, cache, log)
}
}

Expand All @@ -482,8 +473,7 @@ object `package` {
log: Option[Ref[F, Log]]
)(
implicit
C: ConcurrentEffect[F],
CS: ContextShift[F],
C: Concurrent[F],
T: Timer[F]
): F[List[Request]] =
for {
Expand Down Expand Up @@ -527,8 +517,7 @@ object `package` {
log: Option[Ref[F, Log]]
)(
implicit
C: ConcurrentEffect[F],
CS: ContextShift[F],
C: Concurrent[F],
T: Timer[F]
): F[List[Request]] =
for {
Expand Down Expand Up @@ -581,8 +570,7 @@ object `package` {
e: BatchExecution
)(
implicit
C: ConcurrentEffect[F],
CS: ContextShift[F],
C: Concurrent[F],
T: Timer[F]
): F[BatchedRequest] = {
val batches = NonEmptyList.fromListUnsafe(
Expand Down
4 changes: 2 additions & 2 deletions shared/src/main/scala/syntax.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@ object syntax {
/** Implicit syntax to lift any value to the context of Fetch via pure */
implicit class FetchIdSyntax[A](val a: A) extends AnyVal {

def fetch[F[_] : ConcurrentEffect]: Fetch[F, A] =
def fetch[F[_] : Concurrent]: Fetch[F, A] =
Fetch.pure[F, A](a)
}

/** Implicit syntax to lift exception to Fetch errors */
implicit class FetchExceptionSyntax[B](val a: Throwable) extends AnyVal {

def fetch[F[_] : ConcurrentEffect]: Fetch[F, B] =
def fetch[F[_] : Concurrent]: Fetch[F, B] =
Fetch.error[F, B](a)
}
}
Expand Down
Loading

0 comments on commit 2dee74b

Please sign in to comment.