GithubHelp home page GithubHelp logo

kittens's Introduction

Kittens: automatic type class derivation for Cats

Kittens is a Scala library which provides instances of type classes from the Cats library for arbitrary algebraic data types (ADTs) using shapeless-based automatic type class derivation. It also provides utility functions related to Applicative such as lift, traverse and sequence to HList, Record and case classes.

kittens image

Kittens is part of the Typelevel family of projects. It is an Open Source project under the Apache License v2, hosted on GitHub. Binary artifacts will be published to the Sonatype OSS Repository Hosting service and synced to Maven Central.

It is available for Scala 2.12 and 2.13, Scala.js 1.5 and Scala Native 0.4.

To get started with sbt, add the following to your build.sbt file:

libraryDependencies += "org.typelevel" %% "kittens" % "latestVersion" // indicated in the badge below

Typelevel library Build status Gitter channel Scala.js Latest version

Scala 2

Instance derivations are available for the following type classes:

  • Eq, PartialOrder, Order, Hash
  • Show, pretty Show
  • Empty, EmptyK (from Alleycats)
  • Semigroup, CommutativeSemigroup, SemigroupK
  • Monoid, CommutativeMonoid, MonoidK
  • Functor, Contravariant, Invariant
  • Pure (from Alleycats), Apply, Applicative
  • Foldable, Reducible
  • Traverse, NonEmptyTraverse
  • ConsK (from Alleycats)

See the Type class support matrix for more details.

Derivation examples

scala> import cats.implicits._, cats._, cats.derived._

scala> case class Cat[Food](food: Food, foods: List[Food])
defined class Cat

scala> val cat = Cat(1, List(2, 3))
cat: Cat[Int] = Cat(1,List(2, 3))

Derive Functor

scala> implicit val fc: Functor[Cat] = semiauto.functor
FC: cats.Functor[Cat] = cats.derived.MkFunctor2$$anon$4@1c60573f

scala> cat.map(_ + 1)
res0: Cat[Int] = Cat(2,List(3, 4))

Derive Show

Note that the derived Show also prints out field names, so it might be preferable to the default toString:

scala> case class Address(street: String, city: String, state: String)
scala> case class ContactInfo(phoneNumber: String, address: Address)
scala> case class People(name: String, contactInfo: ContactInfo)

scala> val mike = People("Mike", ContactInfo("202-295-3928", Address("1 Main ST", "Chicago", "IL")))

scala> // existing Show instance for Address
scala> implicit val addressShow: Show[Address] =
         a => s"${a.street}, ${a.city}, ${a.state}"

scala> implicit val peopleShow: Show[People] = semiauto.show // auto derive Show for People

scala> mike.show
res0: String = People(name = Mike, contactInfo = ContactInfo(phoneNumber = 202-295-3928, address = 1 Main ST, Chicago, IL))

Note that in this example, the derivation generated instances for all referenced classes but still respected the existing instance in scope. For different ways to derive instances, please see Derivation on Scala 2 below.

Sequence examples

Note that to run these examples, you need partial unification enabled. For Scala 2.12 you should add the following to your build.sbt:

scalacOptions += "-Ypartial-unification"
scala> import cats.implicits._, cats.sequence._
import cats.implicits._
import cats.sequence._

scala> val f1 = (_: String).length
f1: String => Int = <function1>

scala> val f2 = (_: String).reverse
f2: String => String = <function1>

scala> val f3 = (_: String).toFloat
f3: String => Double = <function1>

scala> val f = sequence(f1, f2, f3)
f: String => shapeless.::[Int,shapeless.::[String,shapeless.::[Float,shapeless.HNil]]] = <function1>

scala> f("42.0")
res0: shapeless.::[Int,shapeless.::[String,shapeless.::[Float,shapeless.HNil]]] = 4 :: 0.24 :: 42.0 :: HNil

//or generic over ADTs
scala>  case class MyCase(a: Int, b: String, c: Float)
defined class MyCase

scala>  val myGen = sequenceGeneric[MyCase]
myGen: cats.sequence.sequenceGen[MyCase] = cats.sequence.SequenceOps$sequenceGen@63ae3243

scala> val f = myGen(a = f1, b = f2, c = f3)
f: String => MyCase = <function1>

scala> f("42.0")
res1: MyCase = MyCase(4,0.24,42.0)

Traverse works similarly except you need a shapeless.Poly.

Lift examples

scala> import cats._, implicits._, lift._
import cats._
import implicits._
import lift._

scala> def foo(x: Int, y: String, z: Float) = s"$x - $y - $z"

scala> val lifted = Applicative[Option].liftA(foo _)
lifted: (Option[Int], Option[String], Option[Float]) => Option[String] = <function3>

scala> lifted(Some(1), Some("a"), Some(3.2f))
res0: Option[String] = Some(1 - a - 3.2)

Derivation on Scala 2

There are three options for type class derivation on Scala 2: cats.derived.auto, cats.derived.cached and cats.derived.semiauto. The recommended best practice is to use semiauto:

import cats.derived

implicit val showFoo: Show[Foo] = derived.semiauto.show

This will respect all existing instances even if the field is a type constructor. For example Show[List[A]] will use the native Show instance for List and derived instance for A. And it manually caches the result to val showFoo.

Auto derivation

import derived.auto.show._

A downside is that it will derive an instance from scratch for every use site, increasing compilation time.

Cached derivation

import derived.cached.show._

Use this one with caution - it caches the derived instance globally. So it's only applicable if the instance is global in the application. This could be problematic for libraries, which have no control over the uniqueness of an instance at use site.

Semiauto derivation (recommended)

implicit val showFoo: Show[Foo] = derived.semiauto.show

A downside is we need to write one for every type that needs an instance.

Scala 3

There are five options for type class derivation on Scala 3. The recommended way is to import cats.derived.* and use derives clauses.

In contrast to Scala 2:

  • Cached derivation is not supported (and also not necessary)
  • Type Class Derivation is supported
  • A strict mode is available for semiauto and derives clauses

derives clause (recommended)

Kittens supports Scala 3's derivation syntax. Similar to Scala 2, instances will be derived recursively if necessary.

import cats.derived.*

// No instances declared for Name
case class Name(value: String)
case class Person(name: Name, age: Int) derives Eq, Show

enum CList[+A] derives Functor:
  case CNil
  case CCons(head: A, tail: CList[A])

Note that the derives clause has a fundamental limitation: it generates an instance that requires the type class for all type parameters, even if not necessary. The following example shows a rough equivalent of how a derives Monoid clause is desugared:

case class Concat[+A](left: Vector[A], right: Vector[A])
object Concat:
  // Note that the `Monoid[A]` requirement is not needed,
  // because `Monoid[Vector[A]]` is defined for any `A`.
  given [A: Monoid]: Monoid[Concat[A]] = Monoid.derived

In such cases it is recommended to use semiauto derivation, described below.

Semiauto derivation

This looks similar to semiauto for Scala 2. Instances will be derived recursively if necessary.

import cats.derived.semiauto

// No instances declared for Name
case class Name(value: String)
case class Person(name: Name, age: Int)

object Person:
  given Eq[Person] = semiauto.eq
  given Show[Person] = semiauto.show

enum CList[+A]:
  case CNil
  case CCons(head: A, tail: CList[A])
  
object CList:
  given Functor[CList] = semiauto.functor

Strict derives clause

Similar to derives above, but instances are not derived recursively (except for enums and sealed traits). Users need to be more explicit about which types implement an instance.

import cats.derived.strict.*

// The instances for Name need to be declared explicitly
case class Name(value: String) derives Eq, Show
case class Person(name: Name, age: Int) derives Eq, Show

// A coproduct type (enum) needs only a top-level declaration
enum CList[+A] derives Functor:
  case CNil
  case CCons(head: A, tail: CList[A])

The same limitations apply as with the default derives clause.

Strict semiauto derivation

Similar to semiauto above, but instances are not derived recursively (except for enums and sealed traits). Users need to be more explicit about which types implement an instance.

import cats.derived.strict

case class Name(value: String)
case class Person(name: Name, age: Int)

object Person:
  // The instances for Name need to be declared explicitly
  given Eq[Name] = strict.semiauto.eq
  given Show[Name] = strict.semiauto.show
  given Eq[Person] = strict.semiauto.eq
  given Show[Person] = strict.semiauto.show

enum CList[+A]:
  case CNil
  case CCons(head: A, tail: CList[A])
  
object CList:
  // A coproduct type (enum) needs only a top-level declaration
  given Functor[CList] = semiauto.functor

Auto derivation

This looks similar to auto for Scala 2.

import cats.derived.auto.eq.given
import cats.derived.auto.show.given
import cats.derived.auto.functor.given

case class Name(value: String)
case class Person(name: Name, age: Int)

enum CList[+A]:
  case CNil
  case CCons(head: A, tail: CList[A])

Caveats

Nested type constructors

We are currently unable to derive instances for nested type constructors, such as Functor[[x] =>> List[Set[x]]].

Stack safety

Our derived instances are not stack-safe. This is a departure from the behaviour for Scala 2 because we didn't want to incur the performance penalty of trampolining all instances in cats.Eval. If your data-type is recursive or extremely large, then you may want to write instances by hand instead.

Missing features

Kittens for Scala 3 is built on top of Shapeless 3 which has a completely different API than Shapeless 2, so we don't support features like Sequence and Lift.

ConsK derivation is also not supported, although we expect this to be added in a future release.

Type class support matrix

Legend:

  • โˆ€ - all must satisfy a constraint
  • โˆƒ - at least one must satisfy a constraint
  • โˆƒ! - exactly one must satisfy a constraint
  • โˆง - both constraints must be satisfied
  • โˆจ - either constraint must be satisfied

For monomorphic types

Type Class Case Classes Sealed Traits Singleton types
CommutativeMonoid โˆ€ fields: CommutativeMonoid โœ— โœ—
CommutativeSemigroup โˆ€ fields: CommutativeSemigroup โœ— โœ—
Empty โˆ€ fields: Empty โˆƒ! variant: Empty โœ—
Eq โˆ€ fields: Eq โˆ€ variants: Eq โœ“
Hash โˆ€ fields: Hash โˆ€ variants: Hash โœ“
Monoid โˆ€ fields: Monoid โœ— โœ—
Order โˆ€ fields: Order โˆ€ variants: Order โœ“
PartialOrder โˆ€ fields: PartialOrder โˆ€ variants: PartialOrder โœ“
Semigroup โˆ€ fields: Semigroup โœ— โœ—
Show โˆ€ fields: Show โˆ€ variants: Show โœ“
ShowPretty โˆ€ fields: ShowPretty โˆ€ variants: ShowPretty โœ“

For polymorphic types

Type Class Case Classes Sealed Traits Constant Types ฮป[x => T] Nested Types ฮป[x => F[G[x]]]
Applicative โˆ€ fields: Applicative โœ— for T: Monoid for F: Applicative and G: Applicative
Apply โˆ€ fields: Apply โœ— for T: Semigroup for F: Apply and G: Apply
Contravariant โˆ€ fields: Contravariant โˆ€ variants: Contravariant for any T for F: Functor and G: Contravariant
EmptyK โˆ€ fields: EmptyK โˆƒ! variant: EmptyK for T: Empty for F: EmptyK and any G โˆจ for F: Pure and G: EmptyK
Foldable โˆ€ fields: Foldable โˆ€ variants: Foldable for any T for F: Foldable and G: Foldable
Functor โˆ€ fields: Functor โˆ€ variants: Functor for any T for F: Functor and G: Functor โˆจ for F: Contravariant and G: Contravariant
Invariant โˆ€ fields: Invariant โˆ€ variants: Invariant for any T for F: Invariant and G: Invariant
MonoidK โˆ€ fields: MonoidK โœ— for T: Monoid for F: MonoidK and any G โˆจ for F: Applicative and G: MonoidK
NonEmptyTraverse โˆƒ field: NonEmptyTraverse โˆง โˆ€ fields: Traverse โˆ€ variants: NonEmptyTraverse โœ— for F: NonEmptyTraverse and G: NonEmptyTraverse
Pure โˆ€ fields: Pure โœ— for T: Empty for F: Pure and G: Pure
Reducible โˆƒ field: Reducible โˆง โˆ€ fields: Foldable โˆ€ variants: Reducible โœ— for F: Reducible and G: Reducible
SemigroupK โˆ€ fields: SemigroupK โœ— for T: Semigroup for F: SemigroupK and any G โˆจ for F: Apply and G: SemigroupK
Traverse โˆ€ fields: Traverse โˆ€ variants: Traverse for any T for F: Traverse and G: Traverse
Scala 3 only โ†“
NonEmptyAlternative โˆ€ fields: NonEmptyAlternative โœ— โœ— for F: NonEmptyAlternative and G: Applicative
Alternative โˆ€ fields: Alternative โœ— โœ— for F: Alternative and G: Applicative

Participation

The Kittens project supports the Scala code of conduct and wants all of its channels (mailing list, Gitter, GitHub, etc.) to be welcoming environments for everyone.

Building kittens

Kittens is built with SBT 1.x, and its master branch is built with Scala 2.13 by default.

Contributors

kittens's People

Contributors

andrzejressel avatar armanbilge avatar ashawley avatar atry avatar bplommer avatar ceedubs avatar clydemachine avatar jatcwang avatar jorokr21 avatar kailuowang avatar larsrh avatar letalvoj avatar macalinao avatar mergify[bot] avatar michaelt293 avatar milessabin avatar qi77qi avatar scala-steward avatar stephen-lazaro avatar steven0351 avatar systemfw avatar taig avatar timwspence avatar tkroman avatar travisbrown avatar travisbrown-stripe avatar typelevel-steward[bot] avatar vendethiel avatar vlovgr avatar xuwei-k 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

kittens's Issues

Remove unnecessary uses of Lazy

For recursive types it seems that only Lazy[gen.Repr] is necessary. Lazy[H] and Lazy[T] for H :: T as well as Lazy[L] and Lazy[R] for L :+: R are redundant and we can remove them. Or at least we should remove Lazy[T] and Lazy[R] since there is no chance to see the same type in this position. The problem with Lazy is that it increases compile time and memory footprint. Even worse thanks to milessabin/shapeless#776 which makes it difficult to diagnose OOM errors caused by implicit resolution for large ADTs.

Monad, Monoid, MonoidK issues

The following code works fine:

import cats.kernel.Monoid
import cats.{Foldable, Functor, Monad, MonoidK}
import cats.derived._

object App {
  sealed trait MyTrait[A]
  case class MyClass1[A](a: A, a1: A, a2: A) extends MyTrait[A]
  case class MyClass2[A](a: A, a1: A) extends MyTrait[A]

  def main(args: Array[String]): Unit = {

    import functor._, functor.legacy._
    println(Functor[MyClass1].map(MyClass1(1, 2, 3))(_ + 1))
    println(Functor[MyTrait].map(MyClass1(1, 2, 3).asInstanceOf[MyTrait[Int]])(_ + 1))
    import cats.syntax.functor._
    println(MyClass1(1, 2, 3).map(_ + 1)) //MyClass1(2,3,4)

    import foldable._, foldable.legacy._
    println(Foldable[MyClass1].foldLeft(MyClass1(1, 2, 3), 0)(_ + _))
    import cats.syntax.foldable._
    println(MyClass1(1, 2, 3).foldLeft(0)(_ + _))
    println(MyClass1(1, 2, 3).asInstanceOf[MyTrait[Int]].foldLeft(0)(_ + _)) // 6
  }
}

But if I try either of the following I have errors:

    import monad._, monad.legacy._
    println(Monad[MyClass1].>>=(MyClass1(1, 2, 3))(x => MyClass1(x + 1, x * 2, x * x)))

    import monoid._, monoid.legacy._
    println(Monoid[MyClass1[Int]].combine(MyClass1(1, 2, 3), MyClass1(4, 5, 6)))

    import monoidk._, monoidk.legacy._
    println(MonoidK[MyClass1].algebra[Int].combine(MyClass1(1, 2, 3), MyClass1(4, 5, 6)))

My build.sbt is

name := "demo"
version := "0.1"
scalaOrganization := "org.typelevel"
scalaVersion := "2.12.3-bin-typelevel-4"
libraryDependencies += "org.typelevel" %% "kittens" % "1.0.0-M11"
scalacOptions += "-Ypartial-unification"
scalacOptions += "-Xlog-implicits"

Error messages:
Monad https://gist.github.com/DmytroMitin/56e495ef5da17d36e103f9253e584d81
Monoid https://gist.github.com/DmytroMitin/9d3bca41a5e567f3c32e715ca928f2a1
MonoidK https://gist.github.com/DmytroMitin/f92cafda428f4608019618a230e76461

Deriving one Show using implicitly passed Show argument

I have following test case:

import cats.implicits._
final case class Test[A](a: A)

val showString: cats.Show[Test[String]] = cats.derived.semi.show[Test[String]] // 1
def showA[A: cats.Show]: cats.Show[Test[A]] = cats.derived.semi.show[Test[A]] // 2

I see that I am able to generate showString value (1). But when I try to generalize (2) I get:

could not find implicit value for parameter ev: cats.derived.MkShow[ammonite.$sess.cmd5.Test[A]]
def gen[A: cats.derived.MkShow]: cats.Show[Test[A]] = cats.derived.semi.show[Test[A]]
                                                                            ^

I am able to generate the right values for other type classes supported by Kittens, so I guess it is something unexpected?

I am using Kittens v1.1.1.

Deriving Order

There seems to be no support for deriving Order. Is it just an oversight, or is a technical issue preventing it? shapeless-contrib used to have support for scalaz Order.

Improve @implicitNotFound error messages

I copied the code from documentation and replaced Show with Eq:

import cats._

case class A(a: Int)

object Test {
  implicit val eq: Eq[A] = {
    import derived.auto.eq._
    derived.semi.eq
  }
}

Compiled with Scala 2.12.4 (https://scastie.scala-lang.org/z4Ksxl4lR26ECCDjmGSAxQ)

Adaptation of argument list by inserting () is deprecated: this is unlikely to be what you want.
        signature: OrElse.primary[A, B](implicit a: A): shapeless.OrElse[A,B]
  given arguments: 
 after adaptation: OrElse.primary((): Unit)

ambiguous implicit values:
 both value mkEqHnil in class MkEqDerivation of type => cats.derived.MkEq[shapeless.HNil]
 and value mkEqCnil in class MkEqDerivation of type => cats.derived.MkEq[shapeless.CNil]
 match expected type cats.derived.MkEq[A]

could not find implicit value for parameter ev: cats.derived.MkEq[A]

not enough arguments for method eq: (implicit ev: cats.derived.MkEq[A])cats.Eq[A].
Unspecified value parameter ev.

Remove ConsK

ConsK is not part of cats or alleycats and it was used only for the derivation of Apply, Applicative and Monad which are removed since 2b1cf75. I guess that makes ConsK kind of dead code so we can remove it too.

`sequenceGeneric` doesn't handle out-of-order named params

For example:

import cats.implicits._
import cats.sequence._

case class Config(foo: String, bar: String)
val seq = sequenceGeneric[Config]

// this is fine
seq(foo = Option("a"), bar = Option("b"))
// but this doesn't compile, and gives a rather noisy error
seq(bar = Option("b"), foo = Option("a"))

One big advantage of the named-parameter syntax is that we don't need to care which order the parameters were declared in -- would be particularly useful here.

Restore full auto derivation

Let's divide and conquer.
Similar to #73
If you have time, pick one and comment here:

  • Empty
  • EmptyK
  • Eq
  • Foldable
  • Functor
  • Monoid
  • MonoidK
  • Pure
  • Semigroup
  • SemigroupK
  • Show

Show instance derivation of GADT defined in companion object

I have a problem with Show instance derivation of GADT defined in companion object.
Here is the example:

scala 2.13.1 // but I think, I see the same issue on scala 2.12.8
kittens 2.0.0
object example {

  import cats.Show
  import cats.derived.semi
  import cats.implicits._

  sealed trait Response
  object Response {

    final case class OK(message: String) extends Response
    final case class Failure(message: String, error: Error[Int]) extends Response

    sealed trait Error[T]
    object Error {
      final case class Recoverable(cause: Int) extends Error[Int]
      case object Unexpected extends Error[Unit]
    }
  }

  implicit val show: Show[Response] = {
    semi.show
  }
}

ends up with compilation error:

Error:(21, 10) diverging implicit expansion for type cats.derived.util.VersionSpecific.Lazy[cats.derived.MkShow[A]]
starting with method catsKernelStdHashForSortedMap in trait SortedMapInstances
    semi.show

The workaround is pretty simple - I have to move Error definition outside of Response companion object:

object example {

  import cats.Show
  import cats.derived.semi
  import cats.implicits._

  sealed trait Response
  object Response {

    final case class OK(message: String) extends Response
    final case class Failure(message: String, error: Error[Int]) extends Response
  }

  sealed trait Error[T]
  object Error {
    final case class Recoverable(cause: Int) extends Error[Int]
    case object Unexpected extends Error[Unit]
  }

  implicit val show: Show[Response] = {
    semi.show
  }
}

Now it compiles.
Also there is no issue when Error is not GADT or have no generic type.

But still, looks like a bug. I'm not pretty sure if this applies to kittens or shapeless, but I think you have now enough info to figure it out ;)

Cheers. Great lib guys.

Traverser doesn't handle HNil

The following examples shows that Traverser doesn't handle HNil properly while Mapper/Sequencer do well.
Is this a design error of Traverser not to include a traversable F[_] type parameter like Sequencer does?

import cats.implicits._
import cats.sequence._
import shapeless._
import shapeless.ops.hlist.Mapper
object toOptionPoly extends Poly1 {
  implicit def kase[T]: Case.Aux[T, Option[T]] = at(_.some)
}
def traverse[T <: HList, TraverserOut](v: T)(
    implicit traverser: Traverser.Aux[T, toOptionPoly.type, TraverserOut]
  ): TraverserOut = traverser(v)
def mapAndSequence[T <: HList, MapperOut <: HList, SequencerOut <: HList](v: T)(
    implicit mapper: Mapper.Aux[toOptionPoly.type, T, MapperOut],
    sequencer: Sequencer.Aux[MapperOut, Option, SequencerOut]
  ): Option[SequencerOut] = sequencer(mapper(v))

traverse(1 :: HNil)
// implicit not found
traverseHList(HNil: HNil)

mapAndSequence(1 :: HNil)
// works fine
mapAndSequence(HNil: HNil)

Full-auto derivation

Previously I've used import cats.derived.eq._ to get fully automatic derivation. This is now deprecated, and the message says to "use cats.derive.eq instead". However, as far as I can tell, that doesn't really replaces the now deprecated solution (it's semi-automatic as described in #63).

Using import cats.derived.MkEq._ seems to work. Is that now the recommended import?

Ambiguity between auto derived and sdtlib instances in Cats

Here is an example with Functor and Option:

import cats.instances.all._
import cats.derived.functor._
cats.Functor[Option] // ambiguous implicits

One possibility would be to export an implicit with the least specific type possible. For Functor that would mean something like this:

implicit def derivedFunctor[F[_]](mk: MkFunctor[F]): Functor[F] = mk

Currently this doesn't work for HKTs because of scala/bug#9445. E.g.

implicitly[Show[Int] <:< (Show[A] forSome { type A })] // works
implicitly[Functor[Option] <:< (Functor[F] forSome { type F[_] })] // doesn't work

The existentials above are involved in the overloading resolution for generic methods which is the mechanism used for implicit resolution in Scala.

Tests for ShowPretty fail on Windows

Currently, newlines are hardcoded to \n in ShowPretty. This results in the tests for ShowPretty failing on Windows. The tests pass on Windows if sys.props("line.separator") is used instead.

Is this a good approach to solve this issue?

[Scala 2] sequenceGeneric should handle variance

Consider this example:

case class Foo(a1: Int, a2: Int)

val seq = sequenceGeneric[Foo]

seq(
  a1 = 1.asRight,
  a2 = 2.asRight[String]
)

This fails to compile:

could not find implicit value for parameter seq: cats.sequence.GenericSequencer[shapeless.labelled.FieldType[Symbol @@ String("a1"),Either[Nothing,Int]] :: shapeless.labelled.FieldType[Symbol @@ String("a2"),Either[String,Int]] :: shapeless.HNil,Playground.Foo]
error after rewriting to Playground.this.seq.applyDynamicNamed("apply")(scala.Tuple2("a1", 1.asRight), scala.Tuple2("a2", 2.asRight[String]))
possible cause: maybe a wrong Dynamic method signature?

I've played with a stripped down version of the Sequencer and sequenceGeneric (long story), and making it contravariant (see the code in #423 (comment)) has solved a similar issue for me. Though I can't say for sure that this would work for the full version.

MonoidK issue

If I change the test to be

case class Bar(s: String)

case class ComplexProduct[T](lbl: Option[Bar], // MkSemigroup.const
                             set: Set[T], // provided
                             fns: Vector[() => T], // MkSemigroup.composedd
                             opt: Eval[Option[T]])

The generation fails, if I leave lbl as Option[String] it works, any ideas what's going on?

StackOverflowError when running FoldableTests

From current master (4982a3b):

[info] FoldableTests:
[info] Exception encountered when attempting to run a suite with class name: cats.derived.FoldableTests *** ABORTED ***
[info] java.lang.StackOverflowError:
[info] at scala.Function1$$anonfun$andThen$1.apply(Function1.scala:52)
[info] at scala.Function1$$anonfun$andThen$1.apply(Function1.scala:52)

[Scala 2] Instances for Records and Unions

Kittens allows us to work with raw HLists and Coproducts:

import cats.instances.all._
import cats.derived.MkMonoid._
import cats.syntax.monoid._

1 :: "a" :: HNil |+| 2 :: "b" ::HNil // ok

However, we can't work with raw Records and Unions:

'k ->> 1 :: HNil |+| 'k ->> 2 :: HNil // no Monoid instance

The same goes for other type classes as well. It mostly boils down to defining an instance for FieldType[K, V]. Are there any objections to adding them?

[Meta] Make kitten an official outside module of Cats?

1.0 has been released for a while, I wonder if it would be a good idea to list it as a Cats module in a separate repo like cats-effect and cats-mtl.
The instance derivation could be very handy for Cats users. And having it there will help adoption.
What do you guys think?

ShowPretty for Map[K, V]

It seems kittens does not derive showPretty for Map[K, V]

https://scastie.scala-lang.org/z8h9qwfrSXu2xIwARJ6ndg

import cats.Show._
import cats.derived
import cats.derived.ShowPretty
import cats.derived.auto.show._

derived.semiauto.showPretty[Map[String, String]]
Could not derive an instance of ShowPretty[A] where A = Map[String,String].
Make sure that A satisfies one of the following conditions:
  * it is a case class where all fields have a Show instance
  * it is a sealed trait where all subclasses have a Show instance

2.12 build

Would be great to have a 2.12 build. However it seems it's not as easy as extending the cross versions setting :)

[Scala 2] Sequencer: Implicit complexity explodes when there is no common functor

Consider this example:

implicitly[Sequencer[
  Either[Nothing, Int] ::
    Option[Int] ::
    Option[Int] ::
    Option[Int] ::
    Option[Int] ::
    Option[Int] ::
    Option[Int] ::
    Option[Int] ::
    Option[Int] ::
    Option[Int] ::
    Option[Int] ::
    HNil
]]

The functors here don't align, which leads to a compilation error. But on my machine, it took the compiler 21 seconds to arrive to that conclusion. This gets exponentially worse the more elements the list has, which makes sequence, sequenceGeneric etc. a pain to use for large structures.

This might be caused by the overly general derivation for Sequencer that has cases for Invariant and InvariantMonoidal typeclasses in addition to the obvious Apply. In comparison, with this stripped-down version of Sequencer the example takes less than a second to (fail to) compile:

trait Sequencer[-L <: HList] {
  type F[_]
  type LOut <: HList
  type Out = F[LOut]

  def apply(hl: L): Out
}

object Sequencer {

  type Aux[L <: HList, F0[_], LOut0 <: HList] = Sequencer[L] {
    type F[A] = F0[A]
    type LOut = LOut0
  }

  implicit def hNil[F0[_]](implicit F0: Applicative[F0]): Aux[HNil, F0, HNil] = new Sequencer[HNil] {
    override type F[A] = F0[A]
    override type LOut = HNil

    override def apply(hl: HNil): Out = F0.pure(HNil)
  }

  implicit def hCons[F0[_], H, FT <: HList, T <: HList](
      implicit F0: Applicative[F0],
      tailSequencer: Sequencer.Aux[FT, F0, T]
  ): Aux[F0[H] :: FT, F0, H :: T] = new Sequencer[F0[H] :: FT] {
    override type F[A] = F0[A]
    override type LOut = H :: T

    override def apply(in: F0[H] :: FT): Out =
      F0.map2(in.head, tailSequencer(in.tail): F0[T])(_ :: _)
  }
}

Port for Scala 3

I'm not sure if we should wait for Shapeless 2 to cross compile.
Maybe it's better to start from scratch with Shapeless 3.

Errors deriving show with generic case classes

In Scala 2.13.3, with kittens 2.2.0 (scastie link):

import scala.annotation.nowarn
import cats.{Show, derived}
import com.comcast.ip4s.{Cidr, IpAddress}

case class PortForwarding[+IP <: IpAddress](
  protocol: String,
  internalIpAddress: IP,
  internalPort: Int,
  externalPort: Int,
)

case class Route[+IP <: IpAddress](destination: Cidr[IP], nexthop: IP)

implicit def show1[IP <: IpAddress: Show]: Show[PortForwarding[IpAddress]] = derived.semiauto.show
implicit def show2[IP <: IpAddress: Show]: Show[PortForwarding[IP]] = derived.semiauto.show
implicit def show3[IP <: IpAddress: Show]: Show[Route[IpAddress]] = derived.semiauto.show
implicit def show4[IP <: IpAddress: Show]: Show[Route[IP]] = derived.semiauto.show

show1 compiles successfully. The show2, show3, and show4 all output an error like:

Could not derive an instance of Show[A] where A = Playground.PortForwarding[IP].
Make sure that A has a Typeable instance and satisfies one of the following conditions:
  * it is a case class where all fields have a Show instance
  * it is a sealed trait where all subclasses have a Show instance

I assume this is a bug, especially since show1 compiles but show3 does not.

Removing the variance annotations produces the same result.

The shows for IpAddress and Cidr are defined:

package com.comcast.ip4s
object IpAddress {
  implicit def show[A <: IpAddress]: Show[A] = Show.fromToString[A]
}
object Cidr {
  implicit def show[A <: IpAddress]: Show[Cidr[A]] = Show.fromToString[Cidr[A]]
}

Minimal imports for automatic case-class Monoids

I was pointed at this library in gitter, thanks @joroKr21!

AFAICT, the easiest way to get automatic cats.Monoids for case-classes is:

import cats.derived.monoid._, legacy._

Is that right? I couldn't tell if the "legacy" bit implies there's a better way, / I was hoping for just one import ๐Ÿ˜†

IntelliJ's "Optimize Imports" wants to split these onto two lines:

import cats.derived.monoid._
import cats.derived.monoid.legacy._

and then it marks the first one as unused, so a subsequent "Optimize Imports" will break compilation!

I might file a bug against the IJ scala plugin about that, but if there's another way here I'd like to know as well.

Thanks!

Performance Regression

When updating version numbers for scalaz-and-cats I noticed a fairly stark performance regression for kittens-derived Eq between RC1 and RC3.

For a simple case-class:

case class Foo(age: Int, msg: String, truthy: Boolean)

Previously the deriving syntax was:

implicit val fooEq: Eq[Foo] = derive.eq[Foo]

And now it is:

    implicit val fooEq: Eq[Foo] = {
      import derived.auto.eq._
      derived.semi.eq
    }

For two length-10000 List[Foo] who have the same contents but are not the same object, to compare every element dropped from 38,630 ns to 45,416 ns. Either number is an order of magnitude slower than a hand-written instance, vanilla Scala's ==, or a ScalaZ auto-derived instance.

Save code duplication by using implicit OrElse combinator

#63 introduced changes that make it convenient for the user to derive type classes for nested ADTs without introducing ambiguities, but the cost was some code duplication in Kittens. It just occurred to me that we can eliminate the boilerplate again by using an A OrElse B combinator which is essentially a copy of Either but tries to resolve implicitly first A and then B. Does that make sense?

Unable to derive Foldable.

The following code:

final case class Foo[T](a: Seq[T], b: Option[T])
implicit def fold: Foldable[Foo] = semi.foldable

results in error required MkFoldable[Foo], got MkFoldable[shapeless.Id].
Note that deriving functor with semi.functor works just fine.

scala.MatchError when derive functor

the following code is successfully compiled but fail at runtime( throw MatchError exception)

sealed trait Gadt[A]
final case class Start[A](a: A) extends Gadt[A]
final case class End(a: Int, b: Int) extends Gadt[Int]

class DeriveFunctorTest extends FunSuite {
  import cats.implicits._
  val functor = cats.derived.semi.functor[Gadt]
  test("map test") {
    functor.map(End(1, 10))(_ + 1)
  }
}

by the way, no GADT encoding works fine:

sealed trait Adt[A]
final case class Start[A](a: A) extends Adt[A]
final case class End[A](a: Int, b: Int => A) extends Adt[A]
val functor = cats.derived.semi.functor[Adt]
 functor.map(End(1, _ + 1))(_ + 1)

Compile time benchmarks

It's clear that kittens is a heavy user of CI server cycles, but it would be nice to add some benchmarks for multiple reasons (also considering the soon to come 1.0.0 release):

  • Track / prevent regressions
  • Measure the impact of improvements to scalac
  • Set expectations of users

Kittens 2.0 roadmap (resolve inconsistencies)

We have discussed this already on other issues and on gitter but it's also good to have a dedicated issue. There are a lot of inconsistencies in kittens 1.0 which cannot be solved in a binary compatible way which dictates a 2.0 release to resolve them.

Here is my attempt to list the issues I noticed and propose solutions:

  1. Implicit prioritization scheme:
    There are a few orthogonal decisions to be taken here:

    1. Traits or abstract classes - I think this one is clear. Abstract classes are less powerful and generate less bytecode.
    2. Public or private[derived] visibility - Implicit prioritization is more or less an implementation detail and should be hidden. But we rely on the fact that private[derived] classes are not publicly visible although their methods are. This might be considered hacky. I still lean towards private[derived].
    3. Should we move more instances to the companion objects? - Usually the companion object only contains an apply summoner and the instances start from the first super class. We could save one class per derivation by moving them one level down. I have no strong feelings either way.
  2. Respecting existing instances - I think this one is clear. Hand written code should always take priority. OrElse has proven quite useful here and Functor shows we can do it for HKTs as well.

  3. Handling of recursive types - In most cases this should work out of the box. It looks like there are still implicit parameters that need to be wrapped with Lazy but adding tests will show us where. There are however notable exceptions:

    1. Empty - Calling .empty for a recursive type will almost certainly result in SO. I'm talking about direct recursion here, not something that goes through List or Option for which we can also define EmptyK instances. I don't like the possibility of causing SO in user code but also the chances that someone would define a directly recursive case class (not sealed trait) are low. This is also the reason why I think Empty should not be defined for coproducts.
    2. Monoid - Same issue as Empty. Note that users can always define an implicit lazy val with semi derivation.
    3. EmptyK - I think we should remove the coproduct instances for EmptyK for two reasons: recusive ADTs might cause a SO and it's too arbitrary to pick the first coproduct member in alphabetical order.
  4. Priority of nested instances vs product and coproduct instances for HKTs. - By nested instances I mean instances defined in terms of Split1 (e.g. Functor[a => List[Option[a]]]). I think nested instances should always have higher priority because they give us a higher chance to hit existing instances instead of falling back to coproduct of products. Also it makes sense to me to try first the outer and then the inner instance when both are possible (e.g. MonoidK over Trivial1 vs MonoidK under Applicative).

  5. Naming - There are two competing schemes right now. mkFooHCons (1) vs productDerivedFoo (2). It's not terribly important but I would stick to the majority (1).

I think we should review all derived typeclasses (they are not that many) to make sure the issues are addressed for all of them. I started work on this but would like some opinions on the questions raised.

Tracking progress here:

`MkApplicative` breaks in scala 2.12.3

on scala 2.12.3, we start to get the following error

kittens/extra-tests/src/test/scala/cats/derived/applicative.scala:30: ambiguous implicit values:
[error] both method mkWithoutEmpty in trait MkApply1 of type [F[]](implicit F: shapeless.Cached[cats.Functor[F]], implicit Fdb: shapeless.Cached[cats.Foldable[F]])cats.derived.MkApply[F]
[error] and method mkApplicative in object MkApplicative of type [F[
]](implicit P: alleycats.Pure[F], implicit A: cats.Apply[F])cats.Applicative[F]
[error] match expected type cats.Functor[this.H]
[error] val A = Applicative[IList]
[error] ^

[Scala 3] - Wildcard derivation import not working

Super simple reproduction:

import cats.Show
// import cats.derived.semiauto.{ derived, product } // this works
// import cats.derived.semiauto.{ given, * } // this works (as per the 3rd comment)
import cats.derived.semiauto.* // does not work

enum TradeAction derives Show:
  case Ask, Bid

It fails with the following error:

no implicit argument of type cats.Show[(Playground.TradeAction.Ask : Playground.TradeAction)] was found

The following import might fix the problem:

  import cats.derived.semiauto.product

https://scastie.scala-lang.org/0q4XaUbJTqibfe78MuyzAA


Not sure whether this has already been discussed or not but I figured it might be better to keep track of it right here.

cats.derived.show

it would be useful to have typeclass derivation for cats.Show for (labelled) products and coproducts. I guess even unlabelled products/coproducts could be useful too but I have no usecase for that.

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.