Skip to content

Commit

Permalink
Finish migration to cats-mtl 0.4
Browse files Browse the repository at this point in the history
- Remove hierarchy derivations, as these are causing problems
- Add methods to get instances from effect types directly
- Add transitions from MTL typeclasses to core typeclasses
- Bump version to 0.2.0
  • Loading branch information
oleg-py committed Oct 13, 2018
1 parent 65f8caa commit 04949c3
Show file tree
Hide file tree
Showing 10 changed files with 241 additions and 93 deletions.
49 changes: 44 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ A catpanion library for [cats-mtl] and [cats-effect] providing:

- Easy composition of MTL-style functions
- MTL instances for cats-effect compatible datatypes (e.g. `IO`)
- Conflict-free implicits for sub-instances (e.g. `MonadState` => `Monad`)

Available for Scala 2.11 and 2.12, for Scala JVM and Scala.JS (0.6)

```scala
// Use %%% for scala.js or cross projects
libraryDependencies += "com.olegpy" %% "meow-mtl" % "0.1.3"
libraryDependencies += "com.olegpy" %% "meow-mtl" % "0.2.0"
```

Inspired by [Next-level MTL talk][mtl-talk] and discussions on cats gitter.
Expand Down Expand Up @@ -122,11 +123,13 @@ Supported typeclasses:
#### IMPORTANT!

Don't use `cats.mtl.implicits._` or `cats.mtl.hierarchy.base._` imports.
Hierarchy import is subsumed by `com.olegpy.meow.hierarchy._`. Import
`cats.mtl.instances.all._` and `cats.mtl.syntax.all._` if you need it.
Import `cats.mtl.instances.all._` and `cats.mtl.syntax.all._` if you
need it.

Failure to do this will result in ambiguous implicit instances.

In cats-mtl 0.4.0 hierarchy has been mostly replaced by subtyping. The
remaining hierarchy imports will possibly be [phased out](typelevel/cats-mtl#31)

### Low-level API: optic providers

Expand Down Expand Up @@ -178,6 +181,17 @@ Ref.unsafe[IO, Int](0).runAsk { implicit askInstance =>
}
```

Alternatively, you can pull it out with specific methods if you intend
to use it explicitly or with better-monadic-for implicit patterns:

```scala
implicit val instance: MonadState[IO, Int] =
Ref.unsafe[IO, Int](0).askInstance

// MonadState available below
???
```

### Ref
`Ref` is a referentially transparent variable added in cats-effect
1.0.0-RC2. It supports `MonadState`, `ApplicativeAsk` and `FunctorTell`
Expand Down Expand Up @@ -217,8 +231,8 @@ persistence, notifications, etc.

#### Example: async logger

That logger only waits if a
previous message is still being processed, to ensure correct ordering:
That logger only waits if a previous message is still being processed,
to ensure correct ordering:

```scala
def greeter(name: String)(implicit ev: FunctorTell[IO, String]): IO[Unit] =
Expand All @@ -237,6 +251,31 @@ previous message is still being processed, to ensure correct ordering:
```


## Sub-instances
meow-mtl also provides a set of implicits which let you use
`Monad`/`Applicative`/`Functor` instances if you have an MTL instance of
compatible type (e.g. `MonadState`/`ApplicativeAsk`/`FunctorTell`)

```scala
import cats.implicits._
import com.olegpy.meow.prelude._ // just this one import

// Can use pure and flatTap without having a Monad constraint or pull
// it out manually
def test[F[_]](implicit MS: MonadState[F, Int]): F[Int] =
42.pure[F].flatTap(MS.set)
```

It uses `LowPriority` mechanism from `shapeless` to ensure that _having_
a constraint does not result in ambiguities:

```
import cats.effect.Sync
// Uses Sync as a Monad instance, instead of getting it from MonadState
def test2[F[_]: Sync](implicit MS: MonadState[F, Int]): F[Int] =
42.pure[F].flatTap(MS.set)
```

## License
MIT

Expand Down
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import xerial.sbt.Sonatype._
inThisBuild(Seq(
organization := "com.olegpy",
scalaVersion := "2.12.6",
version := "0.1.4",
version := "0.2.0",
crossScalaVersions := Seq("2.11.12", "2.12.6"),
))

Expand Down
9 changes: 9 additions & 0 deletions src/main/scala/com/olegpy/meow/effects/Consumer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,15 @@ final class Consumer[F[_], A] private (val consume: A => F[Unit]) extends AnyVal
*/
def runTell[B](f: FunctorTell[F, A] => B)(implicit F: Functor[F]): B =
f(new Consumer.TellInstance(this))

/**
* Directly return an instance for `FunctorTell` that is based on this `Consumer`
*
* Returned instance would call the `Consumer` function as its `tell` operation
*
* @see [[runTell]] for potentially more convenient usage
*/
def tellInstance(implicit F: Functor[F]): FunctorTell[F, A] = runTell(identity)
}

object Consumer {
Expand Down
31 changes: 31 additions & 0 deletions src/main/scala/com/olegpy/meow/effects/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,16 @@ package object effects {
def runState[B](f: MonadState[F, A] => B)(implicit F: Monad[F]): B =
f(new RefMonadState(self))

/**
* Directly return an instance for `MonadState` that is based on this `Ref`
*
* Returned instance would use `get`/`set` methods of this `Ref` to manipulate state
*
* @see [[runState]] for potentially more convenient usage
*/
def stateInstance(implicit F: Monad[F]): MonadState[F, A] =
runState(identity)

/**
* Execute an operation requiring some additional context `A` provided within this Ref.
*
Expand Down Expand Up @@ -58,6 +68,16 @@ package object effects {
def runAsk[B](f: ApplicativeAsk[F, A] => B)(implicit F: Applicative[F]): B =
f(new RefApplicativeAsk(self))

/**
* Directly return an instance for `ApplicativeAsk` that is based on this `Ref`
*
* Returned instance would use `get` method of this `Ref` to provide a value
*
* @see [[runAsk]] for potentially more convenient usage
*/
def askInstance(implicit F: Applicative[F]): ApplicativeAsk[F, A] =
runAsk(identity)

/**
* Execute an operation requiring ability to "log" values of type `A`, and,
* potentially, read current value.
Expand Down Expand Up @@ -90,5 +110,16 @@ package object effects {
*/
def runTell[B](f: FunctorTell[F, A] => B)(implicit F: Functor[F], A: Semigroup[A]): B =
f(new RefFunctorTell(self))

/**
* Directly return an instance for `FunctorTell` that is based on this `Ref`
*
* Returned instance would use `modify` method of this `Ref` and a `Semigroup`
* to accumulate results
*
* @see [[runTell]] for potentially more convenient usage
*/
def tellInstance(implicit F: Functor[F], A: Semigroup[A]): FunctorTell[F, A] =
runTell(identity)
}
}
28 changes: 11 additions & 17 deletions src/main/scala/com/olegpy/meow/internal/DerivedHierarchy.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.olegpy.meow.internal

import cats.mtl._
import cats.mtl.hierarchy.{base => conv}
import cats.{ApplicativeError, MonadError}
import com.olegpy.meow.optics.{MkLensToType, MkPrismToType}
import shapeless.{<:!<, =:!=, Coproduct, Refute, Typeable}
Expand Down Expand Up @@ -46,21 +45,6 @@ private[meow] object DerivedHierarchy {
mkPrismToType: MkPrismToType[S, A]
): MonadError[F, A] =
new RaiseOptics.Monad(parent, mkPrismToType())

implicit def askFromState[F[_], L](implicit ev: MonadState[F, L]): ApplicativeAsk[F, L] =
conv.askFromState[F, L]

implicit def tellFromState[F[_], L](implicit ev: MonadState[F, L]): FunctorTell[F, L] =
conv.tellFromState[F, L]

implicit def deriveApplicativeLocal[F[_], S, A](implicit
isAbstractF: IsAbstract[F],
parent: ApplicativeLocal[F, S],
neq: S =:!= A,
mkLensToType: MkLensToType[S, A]
): ApplicativeLocal[F, A] =
new LocalOptics.Applicative(parent, mkLensToType())

}

trait Priority2 extends Priority3 {
Expand All @@ -81,7 +65,7 @@ private[meow] object DerivedHierarchy {
new RaiseOptics.Applicative(parent, mkPrismToType())
}

trait Priority3 {
trait Priority3 extends Priority4 {
implicit def deriveFunctorRaise[F[_], S, A](implicit
isAbstractF: IsAbstract[F],
parent: FunctorRaise[F, S],
Expand All @@ -90,4 +74,14 @@ private[meow] object DerivedHierarchy {
): FunctorRaise[F, A] =
new RaiseOptics.Functor(parent, mkPrismToType())
}

trait Priority4 {
implicit def deriveApplicativeLocal[F[_], S, A](implicit
isAbstractF: IsAbstract[F],
parent: ApplicativeLocal[F, S],
neq: S =:!= A,
mkLensToType: MkLensToType[S, A]
): ApplicativeLocal[F, A] =
new LocalOptics.Applicative(parent, mkLensToType())
}
}
62 changes: 62 additions & 0 deletions src/main/scala/com/olegpy/meow/internal/ParentInstances.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.olegpy.meow.internal

import cats.{Applicative, Functor, Monad}
import cats.mtl._
import shapeless.LowPriority


private[meow] trait ParentInstances extends ParentInstances1 {
implicit def monadStateIsMonad[F[_], S](
implicit LP: LowPriority,
MS: MonadState[F, S]
): Monad[F] = MS.monad
}

private[meow] trait ParentInstances1 extends ParentInstances2 {
implicit def monadChronicleIsMonad[F[_], S](
implicit LP: LowPriority,
MC: MonadChronicle[F, S]
): Monad[F] = MC.monad
}

private[meow] trait ParentInstances2 extends ParentInstances3 {
implicit def applicativeHandleIsApplicative[F[_], S](
implicit LP: LowPriority,
AH: ApplicativeHandle[F, S]
): Applicative[F] = AH.applicative
}

private[meow] trait ParentInstances3 extends ParentInstances4 {
implicit def applicativeAskIsApplicative[F[_], S](
implicit LP: LowPriority,
AA: ApplicativeAsk[F, S]
): Applicative[F] = AA.applicative
}

private[meow] trait ParentInstances4 extends ParentInstances5 {
implicit def applicativeLocalIsApplicative[F[_], S](
implicit LP: LowPriority,
AL: ApplicativeLocal[F, S]
): Applicative[F] = AL.applicative
}

private[meow] trait ParentInstances5 extends ParentInstances6 {
implicit def functorRaiseIsFunctor[F[_], S](
implicit LP: LowPriority,
FR: FunctorRaise[F, S]
): Functor[F] = FR.functor
}

private[meow] trait ParentInstances6 extends ParentInstances7 {
implicit def functorTellIsFunctor[F[_], S](
implicit LP: LowPriority,
FT: FunctorTell[F, S]
): Functor[F] = FT.functor
}

private[meow] trait ParentInstances7 {
implicit def functorListenIsFunctor[F[_], S](
implicit LP: LowPriority,
FL: FunctorListen[F, S]
): Functor[F] = FL.functor
}
6 changes: 6 additions & 0 deletions src/main/scala/com/olegpy/meow/prelude.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.olegpy.meow

import com.olegpy.meow.internal.ParentInstances


object prelude extends ParentInstances
30 changes: 9 additions & 21 deletions src/test/scala/com/olegpy/meow/derivations/Chaining.scala
Original file line number Diff line number Diff line change
Expand Up @@ -33,28 +33,16 @@ object Chaining {
derives[Int]
derives[Long]
}
//
// def testAsk[F[_]](implicit ev: ApplicativeAsk[F, State]): Unit = {
// def derives[S](implicit ev: ApplicativeAsk[F, S]): Unit = ()
//
// derives[State]
// derives[Inner]
// derives[StateComponent]
// derives[String]
// derives[Int]
// derives[Long]
// }

def testStateToAsk[F[_]](implicit ev: MonadState[F, State]): Unit = {
implicitly[ApplicativeAsk[F, State]]
implicitly[ApplicativeAsk[F, Int]]
()
}

def testStateToTell[F[_]](implicit ev: MonadState[F, State]): Unit = {
implicitly[FunctorTell[F, State]]
implicitly[FunctorTell[F, Int]]
()
def testAsk[F[_]](implicit ev: ApplicativeAsk[F, State]): Unit = {
def derives[S](implicit ev: ApplicativeAsk[F, S]): Unit = ()

derives[State]
derives[Inner]
derives[StateComponent]
derives[String]
derives[Int]
derives[Long]
}

case class DbError(text: String)
Expand Down
Loading

0 comments on commit 04949c3

Please sign in to comment.