├── .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 | [](http://travis-ci.org/functional-streams-for-scala/fs2-cats)
7 | [](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 |
--------------------------------------------------------------------------------