Skip to content

Commit

Permalink
Add instances for Finagle services, etc.
Browse files Browse the repository at this point in the history
  • Loading branch information
travisbrown committed Jun 18, 2015
1 parent 12a3046 commit 3404969
Show file tree
Hide file tree
Showing 11 changed files with 90 additions and 18 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ It currently includes the following:

* An injection from [Finagle](https://github.com/twitter/finagle) services to Kleisli arrows over
`Future`
* Category and profunctor instances for `Service`
* Monad instances for `Future` and `Var`
* Semigroup and equality instances for `Future`, `Var`, and `Try`
* A bijection from `Try[A]` to `Xor[Throwable, A]`
Expand Down
19 changes: 11 additions & 8 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ lazy val baseSettings = Seq(
case _ => Nil
}
),
resolvers += Resolver.sonatypeRepo("snapshots"),
wartremoverWarnings in (Compile, compile) ++= Warts.allBut(
Wart.NoNeedForMonad
)
Expand All @@ -46,7 +47,7 @@ lazy val root = project.in(file("."))
git.remoteRepo := "git@github.com:travisbrown/catbird.git"
)
.settings(scalacOptions in (Compile, console) := compilerOptions)
.aggregate(util, finagle)
.aggregate(util, finagle, laws)
.dependsOn(util, finagle)
.dependsOn(
ProjectRef(uri("git://github.com/non/cats.git"), "std")
Expand All @@ -57,6 +58,7 @@ lazy val test = project
.settings(
libraryDependencies ++= Seq(
"com.twitter" %% "bijection-core" % bijectionVersion,
"com.twitter" %% "finagle-core" % finagleVersion,
"com.twitter" %% "util-core" % utilVersion,
"org.scalacheck" %% "scalacheck" % "1.12.2",
"org.scalatest" %% "scalatest" % "2.2.4",
Expand All @@ -67,9 +69,13 @@ lazy val test = project
.dependsOn(
ProjectRef(uri("git://github.com/non/cats.git"), "core"),
ProjectRef(uri("git://github.com/non/cats.git"), "laws"),
ProjectRef(uri("git://github.com/non/cats.git"), "std")
ProjectRef(uri("git://github.com/non/cats.git"), "std"),
util
)
.disablePlugins(CoverallsPlugin)

lazy val laws = project
.settings(buildSettings ++ baseSettings)
.dependsOn(util, finagle, test % "test")

lazy val util = project
.settings(buildSettings ++ baseSettings)
Expand All @@ -80,10 +86,8 @@ lazy val util = project
)
)
.dependsOn(
ProjectRef(uri("git://github.com/non/cats.git"), "core"),
test % "test"
ProjectRef(uri("git://github.com/non/cats.git"), "core")
)
.disablePlugins(CoverallsPlugin)

lazy val finagle = project
.settings(allSettings)
Expand All @@ -92,8 +96,7 @@ lazy val finagle = project
"com.twitter" %% "finagle-core" % finagleVersion
)
)
.dependsOn(util, test % "test")
.disablePlugins(CoverallsPlugin)
.dependsOn(util)

lazy val publishSettings = Seq(
publishMavenStyle := true,
Expand Down
2 changes: 1 addition & 1 deletion finagle/src/main/scala/io/catbird/finagle/package.scala
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
package io.catbird

package object finagle extends ServiceConversions
package object finagle extends ServiceInstances with ServiceConversions
18 changes: 18 additions & 0 deletions finagle/src/main/scala/io/catbird/finagle/service.scala
Original file line number Diff line number Diff line change
@@ -1,11 +1,29 @@
package io.catbird.finagle

import cats.arrow.Category
import cats.data.Kleisli
import cats.functor.Profunctor
import io.catbird.util.futureInstance
import com.twitter.bijection.{ Injection, InversionFailure }
import com.twitter.finagle.Service
import com.twitter.util.{ Future }
import scala.util.Success

trait ServiceInstances {
implicit val serviceInstance: Category[Service] with Profunctor[Service] =
new Category[Service] with Profunctor[Service] {
def id[A]: Service[A, A] = Service.mk(futureInstance.pure)

def compose[A, B, C](f: Service[B, C], g: Service[A, B]): Service[A, C] =
Service.mk(a => g(a).flatMap(f))

def dimap[A, B, C, D](fab: Service[A, B])(f: C => A)(g: B => D): Service[C, D] =
Service.mk(c => fab.map(f)(c).map(g))

override def lmap[A, B, C](fab: Service[A, B])(f: C => A): Service[C, B] = fab.map(f)
}
}

trait ServiceConversions {
implicit def serviceToKleisli[I, O]: Injection[Service[I, O], Kleisli[Future, I, O]] =
Injection.build[Service[I, O], Kleisli[Future, I, O]](Kleisli.kleisli) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,28 @@
package io.catbird.finagle

import cats.std.int._
import cats.Eq
import cats.data.Kleisli
import cats.laws.discipline._
import cats.laws.discipline.eq._
import com.twitter.bijection.InversionFailure
import com.twitter.conversions.time._
import com.twitter.finagle.Service
import com.twitter.util.Future
import io.catbird.test.finagle.{ ArbitraryInstances, EqInstances }
import io.catbird.util._
import org.scalatest.FunSuite
import org.typelevel.discipline.scalatest.Discipline
import scala.util.{ Failure, Success }

/**
* TODO: real tests
*/
class ServiceTests extends FunSuite with ServiceConversions {
class ServiceSuite extends FunSuite with Discipline with
ServiceInstances with ServiceConversions with ArbitraryInstances with EqInstances {
implicit val eq: Eq[Service[Int, Int]] = serviceEq(1.second)

checkAll("Service", CategoryTests[Service].compose[Int, Int, Int, Int])
checkAll("Service", CategoryTests[Service].category[Int, Int, Int, Int])
checkAll("Service", ProfunctorTests[Service].profunctor[Int, Int, Int, Int, Int, Int])

test("Service[Int, String] should round-trip through Kleisli[Future, Int, String]") {
val service = new Service[Int, String] {
def apply(i: Int) = Future.value(i.toString)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ import cats.{ Comonad, Eq }
import cats.laws.discipline._
import com.twitter.conversions.time._
import com.twitter.util.Future
import io.catbird.test.util.ArbitraryKInstances
import io.catbird.test.util.{ ArbitraryKInstances, EqKInstances }
import org.scalatest.FunSuite
import org.typelevel.discipline.scalatest.Discipline

class FutureSuite extends FunSuite with Discipline
with FutureInstances with ArbitraryKInstances {
with FutureInstances with ArbitraryKInstances with EqKInstances {
implicit val eqk: EqK[Future] = futureEqK(1.second)
implicit val eqv: Eq[Future[Int]] = futureEq(1.second)
implicit val comonad: Comonad[Future] = futureComonad(1.second)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,18 @@ import cats.std.int._
import cats.{ Comonad, Eq }
import cats.laws.discipline._
import com.twitter.util.Var
import io.catbird.test.util.ArbitraryKInstances
import io.catbird.test.util.{ ArbitraryKInstances, EqKInstances }
import org.scalatest.FunSuite
import org.typelevel.discipline.scalatest.Discipline

class VarSuite extends FunSuite with Discipline
with VarInstances with ArbitraryKInstances {
with VarInstances with ArbitraryKInstances with EqKInstances {
implicit val eqk: EqK[Var] = varEqK
implicit val eqv: Eq[Var[Int]] = varEq
implicit val comonad: Comonad[Var] = varComonad

checkAll("Var[Int]", MonadTests[Var].monad[Int, Int, Int])
checkAll("Var[Int]", ComonadTests[Var].comonad[Int, Int, Int])
checkAll("Var[Int]", FunctorTests[Var](comonad).functor[Int, Int, Int])
checkAll("Var[Int]", GroupLaws[Var[Int]].semigroup(varSemigroup[Int]))
checkAll("Var[Int]", GroupLaws[Var[Int]].monoid)
}
10 changes: 10 additions & 0 deletions test/src/main/scala/io/catbird/test/finagle/arbitrary.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package io.catbird.test.finagle

import com.twitter.finagle.Service
import io.catbird.util.futureInstance
import org.scalacheck.Arbitrary

trait ArbitraryInstances {
implicit def serviceArbitrary[I, O](implicit arbF: Arbitrary[I => O]): Arbitrary[Service[I, O]] =
Arbitrary(arbF.arbitrary.map(f => Service.mk(i => futureInstance.pure(f(i)))))
}
10 changes: 10 additions & 0 deletions test/src/main/scala/io/catbird/test/finagle/eq.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package io.catbird.test.finagle

import cats.Eq
import com.twitter.finagle.Service
import com.twitter.util.{ Await, Duration }

trait EqInstances {
def serviceEq[I, O](atMost: Duration)(implicit eqF: Eq[I => O]): Eq[Service[I, O]] =
eqF.on(_.andThen(o => Await.result(o, atMost)))
}
18 changes: 18 additions & 0 deletions test/src/main/scala/io/catbird/test/util/eq.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package io.catbird.test.util

import cats.Eq
import cats.laws.discipline.EqK
import com.twitter.util.{ Duration, Future, Var }
import io.catbird.util.{ futureEq, varEq }

trait EqKInstances {
def futureEqK(atMost: Duration): EqK[Future] =
new EqK[Future] {
def synthesize[A](implicit A: Eq[A]): Eq[Future[A]] = futureEq(atMost)
}

implicit def varEqK: EqK[Var] =
new EqK[Var] {
def synthesize[A](implicit A: Eq[A]): Eq[Var[A]] = varEq
}
}

0 comments on commit 3404969

Please sign in to comment.