Git Product home page Git Product logo

cats-tagless's People

Contributors

andrzejressel avatar aoiroaoino avatar armanbilge avatar bpholt avatar cosmin33 avatar daytimewind avatar djspiewak avatar fristi avatar gvolpe avatar hanny24 avatar ivan-klass avatar jentsch avatar jorokr21 avatar kailuowang avatar keirlawson avatar kubukoz avatar larsrh avatar lukajcb avatar m50d avatar marcin-rzeznicki avatar mergify[bot] avatar msinton avatar nigredo-tori avatar pomadchin avatar scala-steward avatar typelevel-steward[bot] avatar vasiliybondarenko avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

cats-tagless's Issues

autoInvariantK fails when function takes lambda

@autoInvariantK fails on this code

@autoInvariantK
trait InvariantFail[F[_]] {

  def function(f: String => F[Boolean]): F[Boolean]
}

Error

[error] object creation impossible, since method function in trait InvariantFail of type (f: String => G[Boolean])G[Boolean] is not defined
[error] method function overrides nothing.
[error] Note: the super classes of <$anon: ore.auth.InvariantFail[G]> contain the following, non final members named function:
[error] def function(f: String => G[Boolean]): G[Boolean]
[error] object creation impossible, since method function in trait InvariantFail of type (f: String => G[Boolean])G[Boolean] is not defined
[error] method function overrides nothing.
[error] Note: the super classes of <$anon: ore.auth.InvariantFail[G]> contain the following, non final members named function:
[error] def function(f: String => G[Boolean]): G[Boolean]
[error] four errors found

moving mainecoon in.

I'd like to complete my wip autoApplyK in mainecoon and then started to move.

flagMap is not a method of F[Int] issue

It might not an issue, but I did every step exactly in the document, added compiler and dependenceis, copied your Increment example. And it failed to compile say flagMap is not a method of F[Int]. Please advice.

replace scala-meta macro annotations with scalamacros

This migration involves three areas of logic, in priority high to low order. We can do these in separate PRs.

  • derivation instances for algebra without abstract type members - one that generates default instances using the cats.tagless.Derive . e.g.
  implicit val functorKForAlg: FunctorK[Alg] =
      Derive.functorK[Alg]

This need to handle situation where Alg with extra type params other than the effect type
The actual derivation is provide by the cats.tagless.Derive from the cats-tagless-macro module, the annotation just need to generate delegate methods (main challenge is to deal with different shapes of Alg types).

cats-tagless-macro doesn't work with bloop

I am not sure if that's a bloop issue or cats-tagless issue, but when I try compilation with bloop compiler, looks like macro annotations are not being expanded.
If I try doing the same with sbt, everything compiles fine.

I've been trying hard to enable more debugging output during macro generation but failed, any hits are appreciated.

I've assembled simplest reproducible case in this repo: https://github.com/ytaras/tagless_failure_showcase

[Scala 2] Support dependent method types

The following does not work:

  trait DepFn[A] {
    type Out
  }

  @autoFunctorK
  trait AlgWithDependentType[F[_]] {
    def dependent[A](f: DepFn[A])(out: f.Out): F[A => f.Out]
  }

Unfortunately couldn't find a way to make it work.

Profunctor/Bifunctor derivation

Adding an @autoProfunctor annotation would require reworking AlgDefn a bit to handle a list of type parameters instead of only one.

Invocation data type and ReifiedInvocations type class

I want to drop a support of Liberator in favour of cats-tagless. But there are two very useful types that I need. And I think they may be a good fit for cats-tagless.

  1. Invocation[M[_[_]], A]
trait Invocation[M[_[_]], A] {
  def invoke[F[_]](target: M[F]): F[A]
}

allows to encode some call of M[F] resulting in F[A] it's like a dual of Free algebra base functor.

  1. ReifiedInvocations[M[_[_]]] type class
trait ReifiedInvocations[M[_[_]]] extends FunctorK[M] { // This inheritance is arguable
  def invocations: M[Invocation[M, ?]]
  def mapInvocations[F[_]](f: Invocation[M, ?] ~> F): M[F] = mapK(invocations, f)
}

allows to create an instance of M[Invocation[M, ?]] that is very useful for transformation of tagless algebras (see Aecor). There is also a macro for its derivation.

If you find this interesting I will send a PR.

P.S. One interesting intuition is that M[F] is a collection of applied F[_] indexed by Invocation[M, A] :)

Support derivation for case class

Example:

case class ServerDependencies[F[_]](
  foo: FooService[F],
  bar: BarService[F],
  other: NotTaglessThing
)

I'd expect it to generate something like:

def mapK[G[_]](f: F ~> G): ServerDependencies[G] =
  ServerDependencies(foo.mapK(f), bar.mapK(f), other)

Having multiple annotations on a trait without a companion throws at runtime

import cats.tagless.implicits._
import cats.tagless.finalAlg
import cats.tagless.autoFunctorK
import cats.arrow.FunctionK

//or autoFunctorK + autoInstrument, or finalAlg + autoInstrument, etc.
@autoFunctorK
@finalAlg
trait Foo[F[_]] {
  def bar(a: Int): F[Int]
}

//object Foo {}

object Main extends App {
  val foo: Foo[cats.Id] = _ => 42

  println(foo.mapK(FunctionK.id))
}

This will result in java.lang.NoClassDefFoundError: Foo$. Uncommenting the companion object fixes things.

Provide method parameter names and values in Instrument

It would be nice to have access to names and values of the parameters for the given call.

Why would this be useful?

  • generating keys for caching
  • adding selected parameters as tags to a distributed tracing span
  • logging selected parameters in an error handler added to the underlying algebra

This will likely be problematic because of the types, but maybe there's something we can do about this.

The basic idea: for an algebra like

trait Foo[F[_]] {
  def bar(a: String, b: Int): F[Int]
}

I imagine we could have a type like

trait Parameter[Constraints[_]] {
  type T
  def constraints: Constraints[T]
  def name: String
  def value: T //by name?
}

included in Instrumentation:

final case class Instrumentation[F[_], Constraints[_], A](
  value: F[A],
  algebraName: String,
  methodName: String,
  parameters: List[List[Parameter[Constraints]]],
)

and somehow define the constraints that we require each parameter to have, at the point of instrumenting:

val foo: Foo[IO] = ???

//Show[String] and Show[Int] would be required here
val instrumented: Foo[Instrumentation[IO, Show, *]] = foo.instrument[Show]

val result = instrumented.bar("baz", 42)

val stringified = result.parameters.head.map { param =>
  param.name + ": " + param.constraints.show(param.value)
}.mkString(", ") //a: "baz", b: 42

I think kinds aren't a problem in this particular case, as parameters will need to be values, so *-kinded things (exactly what Constraints[_] expects).

For multiple implicits (which I don't think would be a common case, as Show should be enough for most usages), one could define a custom case class with derivations from/to it based on other implicits:

final case class Constraints[A](show: Show[A], monoid: Monoid[A])
implicit def constraintsToShow[A](implicit constraints: Constraints[A]): Show[A] = ...

or something like EList (implicit evidence list), as seen in @non's talk: https://youtu.be/O78hnJuzQwA?t=1652

object foo {
  import cats.implicits._
  type Constraints[A] = Show[A] :/: Monoid[A] :/: ENil

  val constraints: Constraints[String] = ... //some implicit derivation?
}

object bar {
  import foo.constraints

  Show[String] //derives from Constraints[String]
}

Derive implementation based on other type class instances

Use case

An example of one of my algebras:

trait Users[F[_]] {
  def login(username: String Refined Email, password: String Refined Password): F[Option[ExchangeResult]]
  def register(user: User, password: String Refined Password): F[Option[UserId]]
}

Each algebra is tested in isolation as most of them have side effects and therefore I test them against a real database.

Now in my service layer I use multiple algebras together to run a specific flow. These flows can fail and therefore a typical service method uses EitherT[F, ErrType, Res]. As I've tested the algebras already I can just stub them. I find myself writing this a lot:

class DummyUsers[F[_]: Applicative] extends Users[F] {
  def login(username: Refined[String, Email], password: Refined[String, Password]): F[Option[ExchangeResult]] =
    Applicative[F].pure(None)
  def register(user: User, password: Refined[String, Password]): F[Option[UserId]] =
    Applicative[F].pure(None)
}

As you can see here I constraint F[_] to be a Applicative such that I can use Id or Writer to be flexible in that regard. When I want to alter behavior, I subclass the DummyUsers class and I override the method.

This however generates a lot of boilerplate

Solution: derive type class implementations

Given I have defined a Default type classes which returns a default value for a given type

trait Default[A] {
  def value: A
}

I have instances defined for basic types. I would like to derive a Default type class instance for my Users

implicitly, macro time ?

Writing this explicitly would look something like this

class UsersDefault extends Users[Default] {
    override def login(
      username: Refined[String, Email],
      password: Refined[String, Password]
    ): Default[Option[ExchangeResult]] = implicitly
    override def register(
      user: User,
      password: Refined[String, Password]
    ): Default[Option[UserId]] = implicitly
  }

As you can see everything is implicitly. Could we use a scalamacro for that to generate such a class?

Ideally I would sub class this such that I can inherit and override certain methods.

lifting

Since we a Default type class instance for Users now I would like to lift the values inside the Applicative. This is a matter of writing a natural transformation of Default ~> F where F[_] : Applicative. This is easy, but how would that work with sub classing? e.g. pseudo code:

object Dummy {

   implicit def defaultToApplicative[F[_] : Applicative]: Default ~> F = ???


   // this class would extend the `Users` algebra and use the `defaultToApplicative` to find a instance of `Users[Default]` which can be turned in to `Users[F]` by applying the transformation
   @GenImpl(Users)
   class DummyUsers[F[_] : Applicative]
}

I would rather avoid using scalameta, as it breaks with scoverage

Closing words

This a rough sketch and a thought experiment to solve the problem. There might be other solutions as well, happy to hear your thoughts!

Support varargs

Currently (0.7) this fails to derive:

@autoFunctorK
trait Foo[F[_]] {
  def bar(is: Int*): F[Int]
}

Documentation - Macro Annotation Section

Since one major part of cats-tagless are the macro annotations, the documentation should explain them clearly. At present, the documentation is a bit mixed in the main page.

For each macro annotation, a documentation page should be written containing the following:

  • An initial one-phrase statement of intent. E.g. "Generates an instance of FunctorK in the companion object.
  • The scope of the annotation: what kind of Scala language constructions (object, trait, case class, etc) it can be applied to.
  • A code snippet, showing an annotated trait or method, followed with another code snippet showing the result of applying the macro (with some manual formatting to avoid gibberish).
  • Dependencies and limitations of the annotation: does it need to appear together with other annotations? Can it handle, for example, type parameters in the trait or the methods.

Add versions of FunctorK and ApplyK for bi- and tri-functors, with macro derivation

Motivation: I'm having to deal with Tapir, using it to generate client and service implementations for some APIs. I end up with values like these:

// Pure specification for an endpoint, used by the client and the server
val fooEndpoint: Endpoint[(AuthToken, FooInput), ApiError, FooOutput, Any]
// Complementary server-side implementation, without authentication.
val fooImpl: (AuthedClient, FooInput) => IO[Either[ApiError, FooOutput]]
// Endpoint + implementation
val fooServerEndpoint: ServerEndpoint[(AuthToken, FooInput), ApiError, FooOutput, Any, IO]

// Authentication, common for all endpoints
val authenticate: AuthToken => IO[Either[ApiError, AuthedClient]]

If you squint hard enough (and if you have enough endpoints to generalize), you can see that the three foo* values belong in three variants of the same shape that describes the API as a whole:

trait Api[F[_, _]] {
  def foo: F[FooInput, FooOutput]
  // more endpoints here
}

type Endpoint0[A, B] = Endpoint[(AuthToken, A), ApiError, B, Any]
type PureApi = Api[Endpoint0]

type Impl[A, B] = (AuthedClient, A) => IO[Either[ApiError, B]]
type ImplApi = Api[Impl]

type ServerEndpoint0[F[_], A, B] = ServerEndpoint[(AuthToken, A), ApiError, B, Any, F]
type ServerApi[F[_]] = Api[ServerEndpoint0[F, *, *]]

Authentication could be factored out if only Api had a version of mapK dealing with bifunctors (bimapK?). In the same vein, combining fooEndpoints and fooImpls is pure boilerplate, and could be implemented using something similar to map2K (bimap2K?).

Similar abstractions for trifunctors might also be useful, in case error types differ between endpoints.

mapK with method name

Currently FunctorK provides mapK:

def mapK[F[_], G[_]](af: Alg[F])(fk: F ~> G): Alg[G]

I've had the use case where I'd like to do a mapK, but have the method name of the Alg (it was a trait) available. A simple example would be me wanting to log every method call on an Alg (using mapK to map to the same effect).

I think it'd be useful to have something like:

def namedMapK[F[_], G[_]](af: Alg[F])(methodName: String, fk: F ~> G): Alg[G]

If this is something you believe makes sense to have, I'd be happy to contribute. If you believe I'm looking at it the wrong way or there's a better approach, I'll also be happy to read your thoughts.

Does Aspect need to be a tagless algebra algebra?

I have been trying out https://github.com/Dwolla/natchez-tagless/tree/main for tracing and been really interested in this idea of typeclass aop. Unfortunately I cannot see how an implementation of Aspect that works on tagless algebras can reason with any algebra that does not have F as the outermost type constructor; A Stream[Instrumentation[F, *], *] doesn't make much sense, and the aspect can't exactly change the return signature into Instrumentation[Stream[F, *], *].

However, what about just reasoning with type-costructors with typeclasses also?
Consider the following implementation of an aspect that allows any structure that embeds an F, and as an example, tracing streams becomes trivial:

trait Aspect[A, Dom[_], Cod[_]] {
  def weave(a: A)(ak: Aspect.Weave[Dom, Cod, *] ~> Id): A
}

object Aspect {
  trait Advice[F[_], G[_]] {
    type A
    def name: String
    def target: F[A]
    implicit def instance: G[A]
  }

  object Advice {
    type Aux[F[_], G[_], A0] = Advice[F, G] { type A = A0 }

    def apply[F[_], G[_], A](
      name: String,
      target: F[A]
    )(implicit instance: G[A]): Aux[F, G, A] = {
      type A0 = A
      val name0 = name
      val target0 = target
      val instance0 = instance
      new Advice[F, G] {
        type A = A0
        val name = name0
        val target = target0
        val instance = instance0
      }
    }
  }

  case class Weave[Dom[_], Cod[_], A](
    algebraName: String,
    domain: List[List[Advice[Eval, Dom]]],
    codomain: Advice.Aux[Id, Cod, A]
  )
}

trait Effect[A] {
  type F[_]
  type U
  def ev: F[U] =:= A
}

object Effect {
  type Aux[F0[_], U0] = Effect[F0[U0]] {
    type F[x] = F0[x]
    type U = U0
  }
}

implicit def effectForAnyKind[F[_], A]: Effect.Aux[F, A] = {
  type F0[B] = F[B]
  new Effect[F[A]] {
    type F[C] = F0[C]
    type U = A
    val ev = implicitly
  }
}

trait TraceableEffect[A] {
  val effect: Effect[A]

  def trace: Trace[effect.F]
  def F: Applicative[effect.F]
}

object TraceableEffect {
  type Aux[F0[_], A0] = TraceableEffect[F0[A0]] {
    val effect: Effect.Aux[F0, A0]
  }
}

implicit def traceableEffectForAnyTraceableEffect[F[_]: Trace, A](implicit
  effect: Effect.Aux[F, A],
  F0: Applicative[F]
): TraceableEffect.Aux[F, A] = {
  val effect0 = effect
  new TraceableEffect[F[A]] {
    val effect = effect0

    def trace = Trace[F]
    def F = F0
  }
}

def applyTracingAspect: Aspect.Weave[Trivial, TraceableEffect, *] ~> Id =
  new (Aspect.Weave[Trivial, TraceableEffect, *] ~> Id) {
    override def apply[A](fa: Aspect.Weave[Trivial, TraceableEffect, A]): Id[A] = {
      val inst = fa.codomain.instance
      val T: Trace[inst.effect.F] = inst.trace
      val ev = inst.effect.ev
      ev {
        T.span(s"${fa.algebraName}.${fa.codomain.name}") {
          ev.flip(fa.codomain.target)
        }
      }
    }
  }

trait MyAlg[F[_]] {
  def getData(id: String): F[String]

  def getDataStream(id: String): fs2.Stream[F, String]
}

def aspectForMyAlg[F[_]: Trace: MonadCancelThrow]: Aspect[MyAlg[F], Trivial, TraceableEffect] = {
  def aspectConstructionMacro[Dom[_], Cod[_]](implicit
    d1: Dom[String],
    c1: Cod[F[String]],
    c2: Cod[fs2.Stream[F, String]]
  ) = new Aspect[MyAlg[F], Dom, Cod] {
    override def weave(a: MyAlg[F])(ak: Aspect.Weave[Dom, Cod, *] ~> Id): MyAlg[F] =
      new MyAlg[F] {
        override def getData(id: String): F[String] =
          ak {
            Aspect.Weave(
              "MyAlg",
              List(List(Aspect.Advice("id", Eval.now(id)))),
              Aspect.Advice[Id, Cod, F[String]]("getData", a.getData(id))
            )
          }

        override def getDataStream(id: String): fs2.Stream[F, String] =
          ak {
            Aspect.Weave(
              "MyAlg",
              List(List(Aspect.Advice("id", Eval.now(id)))),
              Aspect.Advice[Id, Cod, fs2.Stream[F, String]]("getDataStream", a.getDataStream(id))
            )
          }
      }
  }

  aspectConstructionMacro[Trivial, TraceableEffect]
}

def traceMyAlg[F[_]: Trace: MonadCancelThrow](alg: MyAlg[F]): MyAlg[F] =
  aspectForMyAlg[F].weave(alg)(applyTracingAspect)

Semiauto Derivation

It would be very nice to use this with a semiauto encoding in the companion objects. I know circe and shapeless does something like this for Encoder/Decoder/Generic and I'm unsure if it could be leveraged with the existing macro definitions to create something like the below encoding

implicit val functorKAlg = deriveFunctorK[MyAlgebra]

Combination of cats-tagless and scoverage

When you use cats-tagless autoFunctorK on a algebra with scoverage the coverageReport / coverageAggregate bails out:

[error] java.lang.RuntimeException: No source root found for '/Users/markdejong/Projects/test/<macro>' (source roots: '/Users/markdejong/Projects/test/modules/domain/src/main/scala/')
[error]     at scoverage.report.BaseReportWriter.relativeSource(BaseReportWriter.scala:28)
[error]     at scoverage.report.BaseReportWriter.relativeSource(BaseReportWriter.scala:16)
[error]     at scoverage.report.CoberturaXmlWriter.klass(CoberturaXmlWriter.scala:42)
[error]     at scoverage.report.CoberturaXmlWriter.$anonfun$pack$1(CoberturaXmlWriter.scala:66)
[error]     at scala.collection.TraversableLike.$anonfun$map$1(TraversableLike.scala:234)
[error]     at scala.collection.immutable.List.foreach(List.scala:389)
[error]     at scala.collection.TraversableLike.map(TraversableLike.scala:234)
[error]     at scala.collection.TraversableLike.map$(TraversableLike.scala:227)
[error]     at scala.collection.immutable.List.map(List.scala:295)
[error]     at scoverage.report.CoberturaXmlWriter.pack(CoberturaXmlWriter.scala:66)
[error]     at scoverage.report.CoberturaXmlWriter.$anonfun$xml$3(CoberturaXmlWriter.scala:90)
[error]     at scala.collection.TraversableLike.$anonfun$map$1(TraversableLike.scala:234)
[error]     at scala.collection.immutable.List.foreach(List.scala:389)
[error]     at scala.collection.TraversableLike.map(TraversableLike.scala:234)
[error]     at scala.collection.TraversableLike.map$(TraversableLike.scala:227)
[error]     at scala.collection.immutable.List.map(List.scala:295)
[error]     at scoverage.report.CoberturaXmlWriter.xml(CoberturaXmlWriter.scala:90)
[error]     at scoverage.report.CoberturaXmlWriter.write(CoberturaXmlWriter.scala:20)
[error]     at scoverage.ScoverageSbtPlugin$.writeReports(ScoverageSbtPlugin.scala:177)
[error]     at scoverage.ScoverageSbtPlugin$.$anonfun$coverageReport0$1(ScoverageSbtPlugin.scala:124)
[error]     at scoverage.ScoverageSbtPlugin$.$anonfun$coverageReport0$1$adapted(ScoverageSbtPlugin.scala:105)
[error]     at scala.Function1.$anonfun$compose$1(Function1.scala:44)
[error]     at sbt.internal.util.$tilde$greater.$anonfun$$u2219$1(TypeFunctions.scala:40)
[error]     at sbt.std.Transform$$anon$4.work(System.scala:67)
[error]     at sbt.Execute.$anonfun$submit$2(Execute.scala:269)
[error]     at sbt.internal.util.ErrorHandling$.wideConvert(ErrorHandling.scala:16)
[error]     at sbt.Execute.work(Execute.scala:278)
[error]     at sbt.Execute.$anonfun$submit$1(Execute.scala:269)
[error]     at sbt.ConcurrentRestrictions$$anon$4.$anonfun$submitValid$1(ConcurrentRestrictions.scala:178)
[error]     at sbt.CompletionService$$anon$2.call(CompletionService.scala:37)
[error]     at java.util.concurrent.FutureTask.run(FutureTask.java:266)
[error]     at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
[error]     at java.util.concurrent.FutureTask.run(FutureTask.java:266)
[error]     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
[error]     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
[error]     at java.lang.Thread.run(Thread.java:748)

Related scoverage/scalac-scoverage-plugin#210

Add ContravariantK

This makes sense to complete the picture in #56
FunctorK and ContravariantK are dual and would depend on each other for derivation.
Right now FunctorK cannot handle Cokleisli in parameter position.

Generalization of `FunctorK`

Hi! I was searching for something that would resemble this:

def deferK[F[_]: FlatMap](fa: F[Alg[F]]): Alg[F]

but didn't find anything. Long story short, I played around and came up with this:

case class Suspended[Alg[_[_]], F[_], A](effect: Alg[F] => F[A])

trait SuspendK[Alg[_[_]]] {
  def suspendK[F[_], G[_]](fk: Suspended[Alg, F, *] ~> G): Alg[G]
}

which is similar to aspects, but doesn't require an instance to call the attached method.

It turns out suspendK is enough to implement mapK as well as deferK:

//> using scala "2.13.8"
//> using plugin "org.typelevel:::kind-projector:0.13.2"
//> using lib "org.typelevel::cats-core:2.8.0"
//> using lib "org.typelevel::cats-tagless-core:0.14.0"

import cats.FlatMap
import cats.~>
import cats.implicits._
import cats.Id
import cats.data.Nested
import cats.tagless.FunctorK
import cats.tagless.Trivial

case class Suspended[Alg[_[_]], F[_], A](effect: Alg[F] => F[A])

trait SuspendK[Alg[_[_]]] extends FunctorK[Alg] {
  def suspendK[F[_], G[_]](fk: Suspended[Alg, F, *] ~> G): Alg[G]

  def mapK[F[_], G[_]](af: Alg[F])(fk: F ~> G): Alg[G] = suspendK(
    new (Suspended[Alg, F, *] ~> G) {
      def apply[A](fa: Suspended[Alg, F, A]): G[A] = fk(fa.effect(af))
    }
  )

  def deferK[F[_]: FlatMap, G[_]](fa: F[Alg[G]]): Alg[Nested[F, G, *]] =
    suspendK {
      new (Suspended[Alg, G, *] ~> Nested[F, G, *]) {
        def apply[A](fk: Suspended[Alg, G, A]): Nested[F, G, A] =
          fa.map(fk.effect(_)).nested
      }
    }

  def deferKId[F[_]: FlatMap](fa: F[Alg[F]]): Alg[F] =
    suspendK {
      new (Suspended[Alg, F, *] ~> F) {
        def apply[A](fk: Suspended[Alg, F, A]): F[A] =
          fa.flatMap(fk.effect(_))
      }
    }
}

trait MyAlg[F[_]] {
  def foo(s: String): F[Int]
  def bar(i: Int): F[String]
}

object MyAlg {

  implicit val suspendKMyAlg: SuspendK[MyAlg] =
    new SuspendK[MyAlg] {

      def suspendK[F[_], G[_]](fk: Suspended[MyAlg, F, *] ~> G): MyAlg[G] =
        new MyAlg[G] {
          def foo(s: String): G[Int] = fk.apply(Suspended(_.foo(s)))

          def bar(i: Int): G[String] = fk.apply(Suspended(_.bar(i)))

        }
    }

}

Does this seem like a valuable addition? I certainly could use deferKId, not sure how useful the others would be.

As for laws, so far I only have this one:

suspendK(
  new (Suspended[Alg, F, *] ~> F) {
    def apply[A](fa: Suspended[Alg, F, A]): F[A] = fa.effect(af)
  }
) <-> af

Update: I guess this doesn't really have to be a generalization of FunctorK, it could be redefined to have this operation:

def suspend[F[_]]: Alg[Suspended[Alg, F, *]]

then mapK would be inherited from FunctorK normally, and suspendK would be derived like this:

def suspendK[F[_], G[_]](fk: Suspended[Alg, F, *] ~> G): Alg[G] =
  mapK[Suspended[Alg, F, *], G](suspend)(fk)

Default arguments should be supported

Compilation will fail, if the algebra has default arguments.
As an example, the following code will not compile

import cats._, cats.implicits._, cats.data._
import cats.tagless._, cats.tagless.implicits._


trait Alg[F[_]] {
  def test(msg: String = "Hello World"): F[String]
}

object Alg {
  implicit def functorK: FunctorK[Alg] = Derive.functorK[Alg]
}

class AlgInterpreter extends Alg[Id] {
  def test(msg: String) = msg
}

implicit val idToOption = new (Id ~> Option) {
  override def apply[A](a: Id[A]): Option[A] = Some{ a }
}

val idAlg = new AlgInterpreter
val optionAlg = idAlg.mapK(idToOption)

println(idAlg.test())
println(optionAlg.test())

When the default value of the msg argument of the test function is removed, the compilation will succeed.

Publish for scalajs 1.0

Tried to tackle this one but didn't even find the place to change scalajs version. I suspect many things are hidden under sbt-catalysts magic.. I cound use a few pointers from someone who knows more.

Decide what to do about fullyRefined instances

Currently only autoFunctorK and autoInvariantK define fully refined instances in a special fullyRefined object inside the companion object of algebras.

We have at least three options:

  1. Generate only fully refined instances:

    • ๐Ÿ‘ No awkward imports, everything is in the companion object.
    • ๐Ÿ‘Ž It's not clear if that would work in all cases (needs investigation).
  2. Add a fullyRefined flag to macro annotations:

    • ๐Ÿ‘ Easy to opt-in / opt-out for users.
    • ๐Ÿ‘ Easy to distinguish which annotations support fully refined instances.
    • ๐Ÿ‘Ž More complexity on both implementation and usage sides.
  3. Automatically decide whether to generate fully refined instances based on the presence of type members:

    • ๐Ÿ‘ Less overhead for algebras without type members.
    • ๐Ÿ‘ Fully automatic, no need for special flags.
    • ๐Ÿ‘Ž No way to opt-out for users.
    • ๐Ÿ‘Ž Removing all type members later might cause imports to stop compiling.

PROPOSAL - Migrate Scalameta Macros

The macro annotations in cats-tagless are implemented using the macro system of scalameta.

However, the development of macros and macro annotations in Scala Meta was closed, as explained
here.

With these lessons learned, we decided to retire our efforts to build a macro system on top of Scalameta.

Current support for Scalameta macros may not work in next versions of the scala compiler.

On the other hand, the Scala compiler has recently incorporated the macro-paradise compiler plugin, which enables support for macro annotations based on scala-reflect, into their main codebase.

The goal of this issue is to migrate existing macros to the scala.reflect based macro annotations.

Can't derive builder-style methods

trait Example[F[_]] {
  def unit: F[Unit]
  def withFoo(foo: String): Example[F]
}

object Example {
  import cats.tagless._
  implicit val exampleFunctorK: FunctorK[Example] = Derive.functorK[Example]
}

Gives

Error:scalac: overriding method withFoo in trait Example of type (foo: String)mypkg.pkg.Example[G];
 method withFoo has incompatible type

There's a trivial manual implementation here:

implicit val exampleFunctorK: FunctorK[Example] = new FunctorK[Example] {
  def mapK[F[_], G[_]](af: Example[F])(fk: F ~> G): Example[G] =
    new Example[G] {
      def unit: G[Unit] = fk(af.unit)
      def withFoo(foo: String): Example[G] =
        exampleFunctorK.mapK(af.withFoo(foo))(fk)
    }
  }
}

Trouble deriving instances for ApplicativeError/MonadError

def invariantKApplicativeError[E]: InvariantK[ApplicativeError[*[_], E]] =
  Derive.invariantK[ApplicativeError[*[_], E]]

The above results in:

could not find implicit value of type cats.tagless.InvariantK[[F[_]]cats.ApplicativeError.CatchOnlyPartiallyApplied[T,F,E]]

which doesn't seem too helpful...

An attempt to derive MonadError:

def invariantKMonadError[E]: InvariantK[MonadError[*[_], E]] =
  Derive.invariantK[MonadError[*[_], E]]

yields

could not find implicit value of type cats.tagless.InvariantK[[F[_]]E => F[B]]

which is slightly more helpful. Implementing the instance helps, but not a lot:

implicit def functorKFunctionEffect[I, O]: FunctorK[Lambda[F[_] => I => F[O]]] = new FunctorK[Lambda[F[_] => I => F[O]]] {
      def mapK[F[_], G[_]](af: I => F[O])(fk: F ~> G): I => G[O] = i => fk(af(i))
    }
could not find implicit value of type cats.tagless.InvariantK[[F[_]]=> F[Boolean]]

and I implemented that too, but it's not being picked up:

could not find implicit value of type cats.tagless.InvariantK[[F[_]]=> F[Boolean]]

Provide instances for cats core types

e.g.
FunctorK for EitherT, StateT, WriterT,Kleisli
InvariantK for Functor, Apply, FlatMap, etc...
I wonder if they are welcomed in the companion or separate module?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.