├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── build.sbt ├── project ├── build.properties └── plugins.sbt ├── shared └── src │ └── main │ └── scala │ └── fs2 │ └── interop │ └── cats │ ├── IOAsyncInstances.scala │ ├── Instances.scala │ ├── ReverseInstances.scala │ └── cats.scala └── version.sbt /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .idea_modules 3 | target 4 | project/boot 5 | *.swp 6 | *.swo 7 | project.vim 8 | tags 9 | .lib 10 | *~ 11 | *# 12 | z_local.sbt 13 | .DS_Store 14 | /.ensime 15 | /.ensime_cache/ 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: scala 2 | 3 | scala: 4 | - 2.11.8 5 | - 2.12.1 6 | 7 | jdk: 8 | - oraclejdk8 9 | 10 | sudo: false 11 | 12 | cache: 13 | directories: 14 | - $HOME/.ivy2 15 | - $HOME/.sbt 16 | 17 | script: 18 | - sbt ++$TRAVIS_SCALA_VERSION test 19 | 20 | env: 21 | global: 22 | - secure: p0EunkNP/KHM4Cjez+mW2zzAzSAqEJMRZy5zPj4ukCXs7UEGGcvXnT7nEnGy7CUINEwuBhZ/iBsu6GsCzAe3xftRnrz4Yii0bf7RGzfQVum0uU0Gw/ZhDK4e5PIQITL00lDD9esFzxQJ35wKRmzGkDtuXNmPK6n1aEl6XQBuPAkDsqfeYjeoSEnrZvQ3PA7Mkd6Io8H7IxaPn8Gv/CLqBwkzmXSqtC1jnO+EPxK5EPGu8/JNacdRQQdj3mEN+VyWP7TQ7j3F9pEIcBZMEE5cNDgWeVOEdBjr1u7j/A8HqJDawzbCarsg91gQHRbQBczAixHh/NMFkSFN5nICEPqWJL7HAcwZeVpmt/aXogyRAbiuP4zvyA92qovH+LIlu17mdv8jnqa9n9xm/wS2ZQulQhYeMJahxHHYGeIUqczbDgVUV05lT2e4SFE3cRpaY6DlyVqGIkH9gp5lZPfqmk4VVBQimjPdUABzFNquvoTsiiyygLBz36WLZe1V4+hBXWeLoOrXDH6E6lH5KEkwUV1rLr1cC50Lpl4uK/3b2dCsYPMi0iWDpdwoyGX9bz2o04hYArBsRV5R+XKsl5piTK0I2mFRSttiby+4xR1CkeLa8A5kZAJw4jAY0xnwUWptB4WSeGOv3Qjz6I44ph3mUeMKM7XWQ8QsWBSvIJw75dB+rKo= 23 | - secure: FZWqBcFw0x4dUVE2NhoAcc2ByOSgEjAGA6NVGRnDhl/bhgSgZIRb7QbClHh1ljHYwW0s/izDFKMHitRZfcuAwkL7zr6iEcqkl4ICl0BKjn2LttLYio4w3QFnjw9Ro9q5lYiJwL1vdXm/06GOt/S5nE4DrROVk+pCE/clv3z+LPVv5BzhFQocLf8PEYGuKFebsJkLlEJU9UBanN01VMX++Sy+cvkRYxeIauxfONB0ux3C7mI2mEbYl13KJXc6RsxBsyrUdcFmwTub/9LSssja26Nyq9s2EddYvzKzxkjw4fAPbxylJBag3BiYHCGIP9oQifol/NEdJlTZq1Fipes4VEwAwFngT97o+TDmSKdoY4vYbioSTmOTcGiXILloM6QESOubimc4mfpp/XSk9NOePWEmAvBzIZKJbg3hQMHfAmQKI72CmL025b9sfO/0F91PF4oU9w0VTgbmHZVRMXY1eDkl/sbSI474aaYgtQpz9JDLYK+LxG+JrAiayE+ncaygx3dvTia+dhWAP9FVv4dOF1jk3LE5PmjLwI+gnnFSIx/I6IU6SaFA7n6x4YcXesIBMHwhtwIsXi8qyo1O+YdFcmBxwXXSaarxRy8q14Anc+lMw1JzsGr/La3TANgEnm1Jhx8pl63+KrBoJl9w92CWg9MfHBkRHPVQMv7NQNCIDwM= 24 | 25 | after_success: 26 | - '[[ $TRAVIS_BRANCH == "master" ]] && { sbt publish; };' 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 FS2 Contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | FS2 Cats: Interoperability between FS2 and Cats 2 | =============================================== 3 | 4 | **As of FS2 1.0, this project is no longer necessary, as cats support is built in to FS2.** 5 | 6 | [![Build Status](https://travis-ci.org/functional-streams-for-scala/fs2-cats.svg?branch=master)](http://travis-ci.org/functional-streams-for-scala/fs2-cats) 7 | [![Gitter Chat](https://badges.gitter.im/functional-streams-for-scala/fs2.svg)](https://gitter.im/functional-streams-for-scala/fs2) 8 | 9 | This library provides an interoperability layer between FS2 0.9 and Cats. At this time, the API of this library is two imports: 10 | 11 | import fs2.interop.cats._ // Provides conversions from FS2 to Cats (e.g., FS2 Monad to Cats Monad) 12 | import fs2.interop.cats.reverse._ // Provides conversions from Cats to FS2 (e.g., Cats Monad to FS2 Monad) 13 | 14 | Note: importing both of these in to the same lexical scope may cause issues with ambiguous implicits. 15 | 16 | Important: FS2 0.10+ has a direct dependency on Cats and Cats Effect so this library is NOT needed when using 0.10+. 17 | 18 | ### Where to get the latest version ### 19 | 20 | ```scala 21 | // Available for Scala 2.11.11 / 2.12.4 + Cats 1.0.0-RC1 + Cats Effect 0.5 + FS2 0.9 22 | libraryDependencies += "co.fs2" %% "fs2-cats" % "0.5.0" 23 | 24 | // Available for Scala 2.11.11 / 2.12.3 + Cats 1.0.0-MF + Cats Effect 0.4 + FS2 0.9 25 | libraryDependencies += "co.fs2" %% "fs2-cats" % "0.4.0" 26 | 27 | // Available for Scala 2.11.8 / 2.12.1 + Cats 0.9.0 + FS2 0.9 28 | libraryDependencies += "co.fs2" %% "fs2-cats" % "0.3.0" 29 | 30 | // Available for Scala 2.11.8 / 2.12.0 + Cats 0.8.1 + FS2 0.9 31 | libraryDependencies += "co.fs2" %% "fs2-cats" % "0.2.0" 32 | ``` 33 | 34 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | import sbtrelease.Version 2 | import com.typesafe.sbt.pgp.PgpKeys.publishSigned 3 | 4 | lazy val contributors = Seq( 5 | "pchiusano" -> "Paul Chiusano", 6 | "pchlupacek" -> "Pavel Chlupáček", 7 | "alissapajer" -> "Alissa Pajer", 8 | "djspiewak" -> "Daniel Spiewak", 9 | "fthomas" -> "Frank Thomas", 10 | "runarorama" -> "Rúnar Ó. Bjarnason", 11 | "jedws" -> "Jed Wesley-Smith", 12 | "wookietreiber" -> "Christian Krause", 13 | "mpilquist" -> "Michael Pilquist", 14 | "guersam" -> "Jisoo Park" 15 | ) 16 | 17 | val catsVersion = "1.0.0-RC1" 18 | 19 | def scmBranch(v: String): String = { 20 | val Some(ver) = Version(v) 21 | s"v${ver.string}" 22 | } 23 | 24 | lazy val commonSettings = Seq( 25 | name := "fs2-cats", 26 | organization := "co.fs2", 27 | scalaVersion := "2.11.11", 28 | crossScalaVersions := Seq("2.11.11", "2.12.4"), 29 | scalacOptions ++= Seq( 30 | "-feature", 31 | "-deprecation", 32 | "-language:implicitConversions", 33 | "-language:higherKinds", 34 | "-language:existentials", 35 | "-language:postfixOps", 36 | "-Xfatal-warnings", 37 | "-Yno-adapted-args", 38 | "-Ywarn-value-discard", 39 | "-Ywarn-unused-import" 40 | ), 41 | scalacOptions in (Compile, console) ~= {_.filterNot("-Ywarn-unused-import" == _)}, 42 | scalacOptions in (Test, console) := (scalacOptions in (Compile, console)).value, 43 | libraryDependencies ++= Seq( 44 | "co.fs2" %%% "fs2-core" % "0.9.7", 45 | "org.typelevel" %%% "cats-core" % catsVersion, 46 | "org.typelevel" %%% "cats-laws" % catsVersion % "test", 47 | "org.typelevel" %%% "cats-effect" % "0.5" 48 | ), 49 | scmInfo := Some(ScmInfo(url("https://github.com/functional-streams-for-scala/fs2-cats"), "git@github.com:functional-streams-for-scala/fs2-cats.git")), 50 | homepage := Some(url("https://github.com/functional-streams-for-scala/fs2")), 51 | licenses += ("MIT", url("http://opensource.org/licenses/MIT")), 52 | initialCommands := s""" 53 | import fs2._ 54 | import fs2.interop.cats._ 55 | import cats._ 56 | import cats.implicits._ 57 | """, 58 | resolvers += "Sonatype Public" at "https://oss.sonatype.org/content/groups/public/", 59 | addCompilerPlugin("org.spire-math" % "kind-projector" % "0.9.3" cross CrossVersion.binary) 60 | ) ++ testSettings ++ scaladocSettings ++ publishingSettings ++ releaseSettings 61 | 62 | lazy val testSettings = Seq( 63 | parallelExecution in Test := false, 64 | logBuffered in Test := false, 65 | testOptions in Test += Tests.Argument("-verbosity", "2"), 66 | testOptions in Test += Tests.Argument("-minSuccessfulTests", "500"), 67 | publishArtifact in Test := true 68 | ) 69 | 70 | lazy val scaladocSettings = Seq( 71 | scalacOptions in (Compile, doc) ++= Seq( 72 | "-doc-source-url", s"${scmInfo.value.get.browseUrl}/tree/${scmBranch(version.value)}€{FILE_PATH}.scala", 73 | "-sourcepath", baseDirectory.in(LocalRootProject).value.getAbsolutePath, 74 | "-implicits", 75 | "-implicits-show-all" 76 | ), 77 | scalacOptions in (Compile, doc) ~= (_.filterNot(_ == "-Xfatal-warnings")), 78 | autoAPIMappings := true 79 | ) 80 | 81 | lazy val publishingSettings = Seq( 82 | publishTo := { 83 | val nexus = "https://oss.sonatype.org/" 84 | if (version.value.trim.endsWith("SNAPSHOT")) 85 | Some("snapshots" at nexus + "content/repositories/snapshots") 86 | else 87 | Some("releases" at nexus + "service/local/staging/deploy/maven2") 88 | }, 89 | credentials ++= (for { 90 | username <- Option(System.getenv().get("SONATYPE_USERNAME")) 91 | password <- Option(System.getenv().get("SONATYPE_PASSWORD")) 92 | } yield Credentials("Sonatype Nexus Repository Manager", "oss.sonatype.org", username, password)).toSeq, 93 | publishMavenStyle := true, 94 | pomIncludeRepository := { _ => false }, 95 | pomExtra := { 96 | 97 | {for ((username, name) <- contributors) yield 98 | 99 | {username} 100 | {name} 101 | http://github.com/{username} 102 | 103 | } 104 | 105 | }, 106 | pomPostProcess := { node => 107 | import scala.xml._ 108 | import scala.xml.transform._ 109 | def stripIf(f: Node => Boolean) = new RewriteRule { 110 | override def transform(n: Node) = 111 | if (f(n)) NodeSeq.Empty else n 112 | } 113 | val stripTestScope = stripIf { n => n.label == "dependency" && (n \ "scope").text == "test" } 114 | new RuleTransformer(stripTestScope).transform(node)(0) 115 | } 116 | ) 117 | 118 | lazy val releaseSettings = Seq( 119 | releaseCrossBuild := true, 120 | releasePublishArtifactsAction := PgpKeys.publishSigned.value 121 | ) 122 | 123 | lazy val commonJsSettings = Seq( 124 | requiresDOM := false, 125 | scalaJSStage in Test := FastOptStage, 126 | jsEnv in Test := new org.scalajs.jsenv.nodejs.NodeJSEnv(), 127 | scalacOptions in Compile += { 128 | val dir = project.base.toURI.toString.replaceFirst("[^/]+/?$", "") 129 | val url = s"https://raw.githubusercontent.com/functional-streams-for-scala/fs2-cats" 130 | s"-P:scalajs:mapSourceURI:$dir->$url/${scmBranch(version.value)}/" 131 | } 132 | ) 133 | 134 | lazy val noPublish = Seq( 135 | publish := (), 136 | publishLocal := (), 137 | publishSigned := (), 138 | publishArtifact := false 139 | ) 140 | 141 | lazy val root = project.in(file(".")). 142 | settings(commonSettings). 143 | settings(noPublish). 144 | aggregate(fs2CatsJVM, fs2CatsJS) 145 | 146 | lazy val fs2Cats = crossProject.in(file(".")). 147 | settings(commonSettings: _*). 148 | jsSettings(commonJsSettings: _*) 149 | 150 | lazy val fs2CatsJVM = fs2Cats.jvm 151 | lazy val fs2CatsJS = fs2Cats.js 152 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.13 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.typesafe.sbt" % "sbt-osgi" % "0.9.1") 2 | addSbtPlugin("com.github.gseitz" % "sbt-release" % "1.0.4") 3 | addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.0.1") 4 | addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.1.14") 5 | addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "1.1") 6 | addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.19") 7 | 8 | -------------------------------------------------------------------------------- /shared/src/main/scala/fs2/interop/cats/IOAsyncInstances.scala: -------------------------------------------------------------------------------- 1 | package fs2 2 | package interop.cats 3 | 4 | import java.util.concurrent.atomic.{AtomicBoolean, AtomicReference} 5 | 6 | import fs2.internal.{Actor, LinkedMap} 7 | import fs2.util.{Async, Attempt, Effect, Free} 8 | 9 | import _root_.cats.effect.IO 10 | 11 | import scala.concurrent.ExecutionContext 12 | 13 | // mostly cribbed from fs2-scalaz:TaskAsyncInstances.scala 14 | trait IOAsyncInstances { 15 | import IOAsyncInstances._ 16 | 17 | protected class EffectIO extends Effect[IO] { 18 | def pure[A](a: A) = IO.pure(a) 19 | def flatMap[A,B](a: IO[A])(f: A => IO[B]): IO[B] = a flatMap f 20 | override def delay[A](a: => A) = IO(a) 21 | def suspend[A](fa: => IO[A]) = IO.suspend(fa) 22 | def fail[A](err: Throwable) = IO.raiseError(err) 23 | def attempt[A](t: IO[A]) = t.attempt 24 | def unsafeRunAsync[A](t: IO[A])(cb: Attempt[A] => Unit): Unit = t.unsafeRunAsync(cb) 25 | override def toString = "Effect[IO]" 26 | } 27 | 28 | implicit def asyncInstance(implicit ec: ExecutionContext): Async[IO] = new EffectIO with Async[IO] { 29 | def ref[A]: IO[Async.Ref[IO, A]] = CatsIO.ref[A](ec) 30 | override def toString = "Async[IO]" 31 | } 32 | 33 | /* 34 | * Implementation is taken from `fs2` library, with only minor changes. See: 35 | * 36 | * https://github.com/functional-streams-for-scala/fs2/blob/v0.9.0-M2/core/src/main/scala/fs2/util/IO.scala 37 | * 38 | * Copyright (c) 2013 Paul Chiusano, and respective contributors 39 | * 40 | * and is licensed MIT, see LICENSE file at: 41 | * 42 | * https://github.com/functional-streams-for-scala/fs2/blob/series/0.9/LICENSE 43 | */ 44 | private[fs2] object CatsIO { 45 | private type Callback[A] = Either[Throwable, A] => Unit 46 | 47 | private trait MsgId 48 | private trait Msg[A] 49 | private object Msg { 50 | case class Read[A](cb: Callback[(A, Long)], id: MsgId) extends Msg[A] 51 | case class Nevermind[A](id: MsgId, cb: Callback[Boolean]) extends Msg[A] 52 | case class Set[A](r: Either[Throwable, A]) extends Msg[A] 53 | case class TrySet[A](id: Long, r: Either[Throwable, A], 54 | cb: Callback[Boolean]) extends Msg[A] 55 | } 56 | 57 | def ref[A](implicit ec: ExecutionContext): IO[Ref[A]] = IO { 58 | implicit val S = Strategy.fromExecutionContext(ec) 59 | 60 | var result: Either[Throwable, A] = null 61 | // any waiting calls to `access` before first `set` 62 | var waiting: LinkedMap[MsgId, Callback[(A, Long)]] = LinkedMap.empty 63 | // id which increases with each `set` or successful `modify` 64 | var nonce: Long = 0 65 | 66 | lazy val actor: Actor[Msg[A]] = Actor.actor[Msg[A]] { 67 | case Msg.Read(cb, idf) => 68 | if (result eq null) waiting = waiting.updated(idf, cb) 69 | else { val r = result; val id = nonce; ec { cb(r.right.map((_,id))) }; () } 70 | 71 | case Msg.Set(r) => 72 | nonce += 1L 73 | if (result eq null) { 74 | val id = nonce 75 | waiting.values.foreach(cb => ec { cb(r.right.map((_,id))) }) 76 | waiting = LinkedMap.empty 77 | } 78 | result = r 79 | 80 | case Msg.TrySet(id, r, cb) => 81 | if (id == nonce) { 82 | nonce += 1L; val id2 = nonce 83 | waiting.values.foreach(cb => ec { cb(r.right.map((_,id2))) }) 84 | waiting = LinkedMap.empty 85 | result = r 86 | cb(Right(true)) 87 | } 88 | else cb(Right(false)) 89 | 90 | case Msg.Nevermind(id, cb) => 91 | val interrupted = waiting.get(id).isDefined 92 | waiting = waiting - id 93 | val _ = ec { cb (Right(interrupted)) } 94 | } 95 | 96 | new Ref(actor) 97 | } 98 | 99 | class Ref[A] private[fs2](actor: Actor[Msg[A]])(implicit ec: ExecutionContext, protected val F: Async[IO]) extends Async.Ref[IO,A] { 100 | 101 | def access: IO[(A, Either[Throwable,A] => IO[Boolean])] = 102 | IO(new MsgId {}).flatMap { mid => 103 | getStamped(mid).map { case (a, id) => 104 | val set = (a: Either[Throwable,A]) => 105 | IO.async[Boolean] { cb => actor ! Msg.TrySet(id, a, cb) } 106 | (a, set) 107 | } 108 | } 109 | 110 | /** 111 | * Return a `IO` that submits `t` to this ref for evaluation. 112 | * When it completes it overwrites any previously `put` value. 113 | */ 114 | def set(t: IO[A]): IO[Unit] = 115 | IO { ec { t.unsafeRunAsync { r => actor ! Msg.Set(r) } }; () } 116 | def setFree(t: Free[IO,A]): IO[Unit] = 117 | set(t.run(F)) 118 | def runSet(e: Either[Throwable,A]): Unit = 119 | actor ! Msg.Set(e) 120 | 121 | private def getStamped(msg: MsgId): IO[(A,Long)] = 122 | IO.async[(A,Long)] { cb => actor ! Msg.Read(cb, msg) } 123 | 124 | /** Return the most recently completed `set`, or block until a `set` value is available. */ 125 | override def get: IO[A] = IO(new MsgId {}).flatMap { mid => getStamped(mid).map(_._1) } 126 | 127 | /** Like `get`, but returns a `IO[Unit]` that can be used cancel the subscription. */ 128 | def cancellableGet: IO[(IO[A], IO[Unit])] = IO { 129 | val id = new MsgId {} 130 | val get = getStamped(id).map(_._1) 131 | val cancel = IO.async[Unit] { 132 | cb => actor ! Msg.Nevermind(id, r => cb(r.right.map(_ => ()))) 133 | } 134 | (get, cancel) 135 | } 136 | 137 | /** 138 | * Runs `t1` and `t2` simultaneously, but only the winner gets to 139 | * `set` to this `ref`. The loser continues running but its reference 140 | * to this ref is severed, allowing this ref to be garbage collected 141 | * if it is no longer referenced by anyone other than the loser. 142 | */ 143 | def setRace(t1: IO[A], t2: IO[A]): IO[Unit] = IO { 144 | val ref = new AtomicReference(actor) 145 | val won = new AtomicBoolean(false) 146 | val win = (res: Either[Throwable, A]) => { 147 | // important for GC: we don't reference this ref 148 | // or the actor directly, and the winner destroys any 149 | // references behind it! 150 | if (won.compareAndSet(false, true)) { 151 | val actor = ref.get 152 | ref.set(null) 153 | actor ! Msg.Set(res) 154 | } 155 | } 156 | (IO.shift(ec).flatMap(_ => t1)).runAsync(r => IO(win(r))).unsafeRunSync 157 | (IO.shift(ec).flatMap(_ => t2)).runAsync(r => IO(win(r))).unsafeRunSync 158 | } 159 | } 160 | 161 | } 162 | } 163 | 164 | private[fs2] object IOAsyncInstances { 165 | private implicit final class ECSyntax(val ec: ExecutionContext) extends AnyVal { 166 | def apply[A](thunk: => A): Unit = 167 | ec.execute(new Runnable { def run() = { thunk; () } }) 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /shared/src/main/scala/fs2/interop/cats/Instances.scala: -------------------------------------------------------------------------------- 1 | package fs2.interop.cats 2 | 3 | import fs2.util._ 4 | import _root_.cats.{ Functor => CatsFunctor, Monad => CatsMonad, MonadError } 5 | import _root_.cats.arrow.FunctionK 6 | import _root_.cats.data.Kleisli 7 | 8 | trait Instances extends Instances0 { 9 | implicit def effectToMonadError[F[_]](implicit F: Effect[F]): MonadError[F, Throwable] = new MonadError[F, Throwable] { 10 | def pure[A](a: A) = F.pure(a) 11 | override def map[A, B](fa: F[A])(f: A => B) = F.map(fa)(f) 12 | def flatMap[A, B](fa: F[A])(f: A => F[B]) = F.flatMap(fa)(f) 13 | def tailRecM[A, B](a: A)(f: A => F[Either[A,B]]): F[B] = defaultTailRecM(a)(f) 14 | def raiseError[A](t: Throwable) = F.fail(t) 15 | def handleErrorWith[A](fa: F[A])(f: Throwable => F[A]) = F.flatMap(F.attempt(fa))(e => e.fold(f, pure)) 16 | } 17 | 18 | implicit def uf1ToFunctionK[F[_], G[_]](uf1: UF1[F, G]): FunctionK[F, G] = new FunctionK[F, G] { 19 | def apply[A](fa: F[A]) = uf1(fa) 20 | } 21 | 22 | implicit def kleisliSuspendableInstance[F[_], E](implicit F: Suspendable[F]): Suspendable[Kleisli[F, E, ?]] = new Suspendable[Kleisli[F, E, ?]] { 23 | def pure[A](a: A): Kleisli[F, E, A] = Kleisli.pure[F, E, A](a) 24 | override def map[A, B](fa: Kleisli[F, E, A])(f: A => B): Kleisli[F, E, B] = fa.map(f) 25 | def flatMap[A, B](fa: Kleisli[F, E, A])(f: A => Kleisli[F, E, B]): Kleisli[F, E, B] = fa.flatMap(f) 26 | def suspend[A](fa: => Kleisli[F, E, A]): Kleisli[F, E, A] = Kleisli(e => F.suspend(fa.run(e))) 27 | } 28 | } 29 | 30 | private[cats] trait Instances0 extends Instances1 { 31 | implicit def catchableToMonadError[F[_]](implicit F: Catchable[F]): MonadError[F, Throwable] = new MonadError[F, Throwable] { 32 | def pure[A](a: A) = F.pure(a) 33 | override def map[A, B](fa: F[A])(f: A => B) = F.map(fa)(f) 34 | def flatMap[A, B](fa: F[A])(f: A => F[B]) = F.flatMap(fa)(f) 35 | def tailRecM[A, B](a: A)(f: A => F[Either[A,B]]): F[B] = defaultTailRecM(a)(f) 36 | def raiseError[A](t: Throwable) = F.fail(t) 37 | def handleErrorWith[A](fa: F[A])(f: Throwable => F[A]) = F.flatMap(F.attempt(fa))(e => e.fold(f, pure)) 38 | } 39 | 40 | implicit def kleisliCatchableInstance[F[_], E](implicit F: Catchable[F]): Catchable[Kleisli[F, E, ?]] = new Catchable[Kleisli[F, E, ?]] { 41 | def pure[A](a: A): Kleisli[F, E, A] = Kleisli.pure[F, E, A](a) 42 | override def map[A, B](fa: Kleisli[F, E, A])(f: A => B): Kleisli[F, E, B] = fa.map(f) 43 | def flatMap[A, B](fa: Kleisli[F, E, A])(f: A => Kleisli[F, E, B]): Kleisli[F, E, B] = fa.flatMap(f) 44 | def attempt[A](fa: Kleisli[F, E, A]): Kleisli[F, E, Attempt[A]] = Kleisli(e => F.attempt(fa.run(e))) 45 | def fail[A](t: Throwable): Kleisli[F, E, A] = Kleisli(e => F.fail(t)) 46 | } 47 | } 48 | 49 | private[cats] trait Instances1 extends Instances2 { 50 | implicit def monadToCats[F[_]](implicit F: Monad[F]): CatsMonad[F] = new CatsMonad[F] { 51 | def pure[A](a: A) = F.pure(a) 52 | override def map[A, B](fa: F[A])(f: A => B) = F.map(fa)(f) 53 | def flatMap[A, B](fa: F[A])(f: A => F[B]) = F.flatMap(fa)(f) 54 | def tailRecM[A, B](a: A)(f: A => F[Either[A,B]]): F[B] = defaultTailRecM(a)(f) 55 | } 56 | } 57 | 58 | private[cats] trait Instances2 { 59 | 60 | implicit def functorToCats[F[_]](implicit F: Functor[F]): CatsFunctor[F] = new CatsFunctor[F] { 61 | def map[A, B](fa: F[A])(f: A => B) = F.map(fa)(f) 62 | } 63 | 64 | protected def defaultTailRecM[F[_], A, B](a: A)(f: A => F[Either[A,B]]) 65 | (implicit F: Monad[F]): F[B] = 66 | F.flatMap(f(a)) { 67 | case Left(a2) => defaultTailRecM(a2)(f) 68 | case Right(b) => F.pure(b) 69 | } 70 | } 71 | 72 | -------------------------------------------------------------------------------- /shared/src/main/scala/fs2/interop/cats/ReverseInstances.scala: -------------------------------------------------------------------------------- 1 | package fs2.interop.cats 2 | 3 | import fs2.util._ 4 | import _root_.cats.{ Functor => CatsFunctor, Monad => CatsMonad, MonadError } 5 | import _root_.cats.arrow.FunctionK 6 | 7 | trait ReverseInstances extends ReverseInstances0 { 8 | 9 | implicit def monadErrorToCatchable[F[_]](implicit F: MonadError[F, Throwable]): Catchable[F] = new Catchable[F] { 10 | def pure[A](a: A) = F.pure(a) 11 | override def map[A, B](fa: F[A])(f: A => B) = F.map(fa)(f) 12 | def flatMap[A, B](fa: F[A])(f: A => F[B]) = F.flatMap(fa)(f) 13 | def fail[A](t: Throwable) = F.raiseError(t) 14 | def attempt[A](fa: F[A]) = F.handleErrorWith(F.map(fa)(a => Right(a): Either[Throwable, A]))(t => pure(Left(t))) 15 | } 16 | 17 | implicit def functionKToUf1[F[_], G[_]](fk: FunctionK[F, G]): UF1[F, G] = new UF1[F, G] { 18 | def apply[A](fa: F[A]) = fk(fa) 19 | } 20 | } 21 | 22 | private[cats] trait ReverseInstances0 extends ReverseInstances1 { 23 | 24 | implicit def catsToMonad[F[_]](implicit F: CatsMonad[F]): Monad[F] = new Monad[F] { 25 | def pure[A](a: A) = F.pure(a) 26 | override def map[A, B](fa: F[A])(f: A => B) = F.map(fa)(f) 27 | def flatMap[A, B](fa: F[A])(f: A => F[B]) = F.flatMap(fa)(f) 28 | } 29 | } 30 | 31 | private[cats] trait ReverseInstances1 { 32 | 33 | implicit def catsToFunctor[F[_]](implicit F: CatsFunctor[F]): Functor[F] = new Functor[F] { 34 | def map[A, B](fa: F[A])(f: A => B) = F.map(fa)(f) 35 | } 36 | } 37 | 38 | -------------------------------------------------------------------------------- /shared/src/main/scala/fs2/interop/cats/cats.scala: -------------------------------------------------------------------------------- 1 | package fs2 2 | package interop 3 | 4 | import _root_.cats.{ Eq, Monoid, Semigroup } 5 | import _root_.cats.kernel.instances.all._ 6 | 7 | import fs2.util.{ Catchable, Free } 8 | 9 | package object cats extends Instances with IOAsyncInstances { 10 | 11 | object reverse extends ReverseInstances 12 | 13 | implicit class StreamCatsOps[F[_], A](val self: Stream[F, A]) extends AnyVal { 14 | 15 | def changesEq(implicit eq: Eq[A]): Stream[F, A] = 16 | self.filterWithPrevious(eq.neqv) 17 | 18 | def changesByEq[B](f: A => B)(implicit eq: Eq[B]): Stream[F, A] = 19 | self.filterWithPrevious((x, y) => eq.neqv(f(x), f(y))) 20 | 21 | def foldMap[B](f: A => B)(implicit M: Monoid[B]): Stream[F, B] = 22 | self.fold(M.empty)((b, a) => M.combine(b, f(a))) 23 | 24 | def foldMonoid(implicit M: Monoid[A]): Stream[F, A] = 25 | self.fold(M.empty)(M.combine(_, _)) 26 | 27 | def foldSemigroup(implicit S: Semigroup[A]): Stream[F, A] = 28 | self.reduce(S.combine(_, _)) 29 | 30 | def runFoldMapFree[B](f: A => B)(implicit M: Monoid[B]): Free[F, B] = 31 | self.runFoldFree(M.empty)((b, a) => M.combine(b, f(a))) 32 | 33 | def runGroupByFoldMapFree[K, B: Monoid](f: A => K)(g: A => B): Free[F, Map[K, B]] = 34 | runFoldMapFree(a => Map(f(a) -> g(a))) 35 | 36 | def runGroupByFoldMonoidFree[K](f: A => K)(implicit M: Monoid[A]): Free[F, Map[K, A]] = 37 | runFoldMapFree(a => Map(f(a) -> a)) 38 | 39 | def runGroupByFree[K](f: A => K)(implicit M: Monoid[A]): Free[F, Map[K, Vector[A]]] = 40 | runGroupByFoldMapFree(f)(a => Vector(a)) 41 | 42 | def runFoldMap[B](f: A => B)(implicit F: Catchable[F], M: Monoid[B]): F[B] = 43 | runFoldMapFree(f).run 44 | 45 | def runGroupByFoldMap[K, B: Monoid](f: A => K)(g: A => B)(implicit F: Catchable[F]): F[Map[K, B]] = 46 | runGroupByFoldMapFree(f)(g).run 47 | 48 | def runGroupByFoldMonoid[K](f: A => K)(implicit F: Catchable[F], M: Monoid[A]): F[Map[K, A]] = 49 | runGroupByFoldMonoidFree(f).run 50 | 51 | def runGroupBy[K](f: A => K)(implicit F: Catchable[F], M: Monoid[A]): F[Map[K, Vector[A]]] = 52 | runGroupByFree(f).run 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /version.sbt: -------------------------------------------------------------------------------- 1 | version in ThisBuild := "0.5.1-SNAPSHOT" 2 | --------------------------------------------------------------------------------