Skip to content

Commit

Permalink
Deprecate auto in favor of semiauto
Browse files Browse the repository at this point in the history
`semiauto` doesn't use `Lazy`.

That improves compile time performance marginally
and has better error messages on Scala 2.13.
  • Loading branch information
joroKr21 committed Dec 7, 2019
1 parent 2c9182b commit 80bf8f8
Show file tree
Hide file tree
Showing 24 changed files with 415 additions and 244 deletions.
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ cat: Cat[Int] = Cat(1,List(2, 3))
```scala
scala> implicit val fc: Functor[Cat] = {
import auto.functor._
semi.functor }
semiauto.functor }
FC: cats.Functor[Cat] = cats.derived.MkFunctor2$$anon$4@1c60573f

scala> cat.map(_ + 1)
Expand All @@ -82,7 +82,7 @@ scala> implicit val addressShow: Show[Address] = new Show[Address] {

scala> implicit val peopleShow: Show[People] = {
import auto.show._
semi.show
semiauto.show
} //auto derive Show for People

scala> mike.show
Expand Down Expand Up @@ -163,7 +163,7 @@ import cats.derived

implicit val showFoo: Show[Foo] = {
import derived.auto.show._
derived.semi.show
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 the `val showFoo`. Downside user will need to write one for every type they directly need a `Show` instance
Expand All @@ -188,7 +188,7 @@ Use this one with caution. It caches the derived instance globally. So it's only

3. manual semi
```scala
implicit val showFoo: Show[Foo] = derived.semi.show
implicit val showFoo: Show[Foo] = derived.semiauto.show
```
It has the same downside as the recommenced semi-auto practice but also suffers from the type constructor field issue. I.e. if a field type is a type constructor whose native instance relies on the instance of the parameter type, this approach will by default derive an instance for the type constructor one. To overcome this user have to first derive the instance for type parameter.
e.g. given
Expand All @@ -198,8 +198,8 @@ case class Bar(a: String)
```
Since the `bars` field of `Foo` is a `List` of `Bar` which breaks the chains of auto derivation, you will need to derive `Bar` first and then `Foo`
```scala
implicit val showBar: Show[Bar] = semi.show
implicit val showFoo: Show[Foo] = semi.show
implicit val showBar: Show[Bar] = semiauto.show
implicit val showFoo: Show[Foo] = semiauto.show
```
This way the native instance for `Show[List]` would be used.

Expand Down
62 changes: 62 additions & 0 deletions core/src/main/scala/cats/derived/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,7 @@ object cached {
* res3: String = Foo(bars = List(Bar(a = a)))
* }}}
*/
@deprecated(message = "Use semiauto instead.", since = "2.1.0")
object semi {

def empty[A](implicit ev: Lazy[MkEmpty[A]]): Empty[A] = ev.value
Expand Down Expand Up @@ -356,3 +357,64 @@ object semi {
def invariant[F[_]](implicit F: Lazy[MkInvariant[F]]): Invariant[F] = F.value
}

/**
* allows semi automatically derive each instance. The derivation might need help when
* there are fields with type constructor that comes with instances
* e.g.
* {{{
* scala> case class Foo(bars: List[Bar])
* scala> case class Bar(a: String)
*
* scala> cats.derived.semiauto.show[Foo].show(Foo(List(Bar("a"))))
* res1: String = Foo(bars = $colon$colon(head = Bar(a = a), tl$access$1 = Nil.type()))
* }}}
* Note that semi.show didn't respect the native `Show[List]` instance
*
* You could either derive a Bar instance first
* {{{
* scala> implicit val barShow = cats.derived.semi.show[Bar]
*
* scala> cats.derived.semiauto.show[Foo].show(Foo(List(Bar("a"))))
* res2: String = Foo(bars = List(Bar(a = a)))
* }}}
*
* Or you can take advantage of a controlled auto derivation
* {{{
* scala> implicit val fooShow: Show[Foo] = { |
* import cats.derived.auto.show._ |
* cats.derived.semiauto.show |
* }
* scala> Foo(List(Bar("a"))).show
* res3: String = Foo(bars = List(Bar(a = a)))
* }}}
*/
object semiauto {

def eq[A](implicit ev: MkEq[A]): Eq[A] = ev
def partialOrder[A](implicit ev: MkPartialOrder[A]): PartialOrder[A] = ev
def order[A](implicit ev: MkOrder[A]): Order[A] = ev
def hash[A](implicit ev: MkHash[A]): Hash[A] = ev

def show[A](implicit ev: MkShow[A]): Show[A] = ev
def showPretty[A](implicit ev: MkShowPretty[A]): ShowPretty[A] = ev

def empty[A](implicit ev: MkEmpty[A]): Empty[A] = ev
def emptyK[F[_]](implicit F: MkEmptyK[F]): EmptyK[F] = F
def semigroup[T](implicit ev: MkSemigroup[T]): Semigroup[T] = ev
def semigroupK[F[_]](implicit F: MkSemigroupK[F]): SemigroupK[F] = F
def monoid[A](implicit ev: MkMonoid[A]): Monoid[A] = ev
def monoidK[F[_]](implicit F: MkMonoidK[F]): MonoidK[F] = F

def functor[F[_]](implicit F: MkFunctor[F]): Functor[F] = F
def contravariant[F[_]](implicit F: MkContravariant[F]): Contravariant[F] = F
def invariant[F[_]](implicit F: MkInvariant[F]): Invariant[F] = F
def pure[F[_]](implicit F: MkPure[F]): Pure[F] = F
def apply[F[_]](implicit F: MkApply[F]): Apply[F] = F
def applicative[F[_]](implicit F: MkApplicative[F]): Applicative[F] = F

def foldable[F[_]](implicit F: MkFoldable[F]): Foldable[F] = F
def traverse[F[_]](implicit F: MkTraverse[F]): Traverse[F] = F

def consK[F[_]](implicit F: MkConsK[F, F]): ConsK[F] = MkConsK.consK(F)
def iterable[F[_], A](fa: F[A])(implicit F: MkIterable[F]): Iterable[A] = F.iterable(fa)
}
28 changes: 18 additions & 10 deletions core/src/test/scala/cats/derived/applicative.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,10 @@ import cats.laws.discipline.{ApplicativeTests, SerializableTests}
import cats.laws.discipline.SemigroupalTests.Isomorphisms

class ApplicativeSuite extends KittensSuite {
import ApplicativeSuite._
import TestDefns._
import TestEqInstances._

type OptList[A] = Option[List[A]]
type AndInt[A] = (A, Int)
type ListBox[A] = List[Box[A]]

def testApplicative(context: String)(
implicit caseClassWOption: Applicative[CaseClassWOption],
optList: Applicative[OptList],
Expand Down Expand Up @@ -58,12 +55,23 @@ class ApplicativeSuite extends KittensSuite {
}

{
implicit val caseClassWOption: Applicative[CaseClassWOption] = semi.applicative
implicit val optList: Applicative[OptList] = semi.applicative
implicit val andInt: Applicative[AndInt] = semi.applicative
implicit val interleaved: Applicative[Interleaved] = semi.applicative
implicit val listBox: Applicative[ListBox] = semi.applicative
testApplicative("semi")
import semiInstances._
testApplicative("semiauto")
}
}

object ApplicativeSuite {
import TestDefns._

type OptList[A] = Option[List[A]]
type AndInt[A] = (A, Int)
type ListBox[A] = List[Box[A]]

object semiInstances {
implicit val caseClassWOption: Applicative[CaseClassWOption] = semiauto.applicative
implicit val optList: Applicative[OptList] = semiauto.applicative
implicit val andInt: Applicative[AndInt] = semiauto.applicative
implicit val interleaved: Applicative[Interleaved] = semiauto.applicative
implicit val listBox: Applicative[ListBox] = semiauto.applicative
}
}
28 changes: 18 additions & 10 deletions core/src/test/scala/cats/derived/apply.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,10 @@ import cats.laws.discipline.{ApplyTests, SerializableTests}
import cats.laws.discipline.SemigroupalTests.Isomorphisms

class ApplySuite extends KittensSuite {
import ApplySuite._
import TestDefns._
import TestEqInstances._

type OptList[A] = Option[List[A]]
type AndInt[A] = (A, Int)
type ListBox[A] = List[Box[A]]

def testApply(context: String)(
implicit caseClassWOption: Apply[CaseClassWOption],
optList: Apply[OptList],
Expand Down Expand Up @@ -58,12 +55,23 @@ class ApplySuite extends KittensSuite {
}

{
implicit val caseClassWOption: Apply[CaseClassWOption] = semi.apply
implicit val optList: Apply[OptList] = semi.apply
implicit val andInt: Apply[AndInt] = semi.apply
implicit val interleaved: Apply[Interleaved] = semi.apply
implicit val listBox: Apply[ListBox] = semi.apply
testApply("semi")
import semiInstances._
testApply("semiauto")
}
}

object ApplySuite {
import TestDefns._

type OptList[A] = Option[List[A]]
type AndInt[A] = (A, Int)
type ListBox[A] = List[Box[A]]

object semiInstances {
implicit val caseClassWOption: Apply[CaseClassWOption] = semiauto.apply
implicit val optList: Apply[OptList] = semiauto.apply
implicit val andInt: Apply[AndInt] = semiauto.apply
implicit val interleaved: Apply[Interleaved] = semiauto.apply
implicit val listBox: Apply[ListBox] = semiauto.apply
}
}
13 changes: 11 additions & 2 deletions core/src/test/scala/cats/derived/consk.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import org.scalacheck.Arbitrary
import org.scalatestplus.scalacheck.ScalaCheckDrivenPropertyChecks

class ConsKSuite extends KittensSuite with ScalaCheckDrivenPropertyChecks {
import ConsKSuite._
import TestDefns._

def checkConsK[F[_], A : Arbitrary](nil: F[A])(fromSeq: Seq[A] => F[A])(implicit F: ConsK[F]): Unit =
Expand All @@ -44,8 +45,16 @@ class ConsKSuite extends KittensSuite with ScalaCheckDrivenPropertyChecks {
}

{
implicit val iList: ConsK[IList] = semi.consK[IList]
implicit val snoc: ConsK[Snoc] = semi.consK[Snoc]
import semiInstances._
testConsK("semi")
}
}

object ConsKSuite {
import TestDefns._

object semiInstances {
implicit val iList: ConsK[IList] = semiauto.consK[IList]
implicit val snoc: ConsK[Snoc] = semiauto.consK[Snoc]
}
}
43 changes: 25 additions & 18 deletions core/src/test/scala/cats/derived/contravariant.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,10 @@ import cats.laws.discipline.arbitrary._
import cats.laws.discipline.eq._

class ContravariantSuite extends KittensSuite {
import ContravariantSuite._
import TestDefns._
import TestEqInstances._

type OptPred[A] = Option[A => Boolean]
type ListPred[A] = List[A => Boolean]
type GenericAdtPred[A] = GenericAdt[A => Boolean]
type ListSnocF[A] = List[Snoc[A => Int]]
type InterleavedPred[A] = Interleaved[A => Boolean]
type AndCharPred[A] = (A => Boolean, Char)
type TreePred[A] = Tree[A => Boolean]

def testContravariant(context: String)(
implicit
optPred: Contravariant[OptPred],
Expand Down Expand Up @@ -71,17 +64,31 @@ class ContravariantSuite extends KittensSuite {
testContravariant("cached")
}

semiTests.run()
{
import semiInstances._
testContravariant("semiauto")
}
}

object ContravariantSuite {
import TestDefns._

type OptPred[A] = Option[A => Boolean]
type ListPred[A] = List[A => Boolean]
type GenericAdtPred[A] = GenericAdt[A => Boolean]
type ListSnocF[A] = List[Snoc[A => Int]]
type InterleavedPred[A] = Interleaved[A => Boolean]
type AndCharPred[A] = (A => Boolean, Char)
type TreePred[A] = Tree[A => Boolean]

object semiTests {
implicit val optPred: Contravariant[OptPred] = semi.contravariant
implicit val listPred: Contravariant[ListPred] = semi.contravariant
implicit val genericAdtPred: Contravariant[GenericAdtPred] = semi.contravariant
implicit val listSnocF: Contravariant[ListSnocF] = semi.contravariant
implicit val interleavePred: Contravariant[InterleavedPred] = semi.contravariant
implicit val andCharPred: Contravariant[AndCharPred] = semi.contravariant
implicit val treePred: Contravariant[TreePred] = semi.contravariant
def run(): Unit = testContravariant("semi")
object semiInstances {
implicit val optPred: Contravariant[OptPred] = semiauto.contravariant
implicit val listPred: Contravariant[ListPred] = semiauto.contravariant
implicit val genericAdtPred: Contravariant[GenericAdtPred] = semiauto.contravariant
implicit val listSnocF: Contravariant[ListSnocF] = semiauto.contravariant
implicit val interleavePred: Contravariant[InterleavedPred] = semiauto.contravariant
implicit val andCharPred: Contravariant[AndCharPred] = semiauto.contravariant
implicit val treePred: Contravariant[TreePred] = semiauto.contravariant
}
}

39 changes: 21 additions & 18 deletions core/src/test/scala/cats/derived/empty.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,6 @@ class EmptySuite extends KittensSuite {
import EmptySuite._
import TestDefns._

// `Monoid[Option[A]]` gives us `Empty[Option[A]]` but it requires a `Semigroup[A]`.
implicit def emptyOption[A]: Empty[Option[A]] = Empty(None)

def testEmpty(context: String)(
implicit foo: Empty[Foo],
outer: Empty[Outer],
Expand Down Expand Up @@ -74,26 +71,32 @@ class EmptySuite extends KittensSuite {
illTyped("Empty[Rgb]")
}

semiTests.run()

object semiTests {
implicit val foo: Empty[Foo] = semi.empty
implicit val outer: Empty[Outer] = semi.empty
implicit val interleaved: Empty[Interleaved[String]] = semi.empty
implicit val recursive: Empty[Recursive] = semi.empty
implicit val iList: Empty[IList[Dummy]] = semi.empty
implicit val snoc: Empty[Snoc[Dummy]] = semi.empty
implicit val box: Empty[Box[Mask]] = semi.empty
implicit val chain: Empty[Chain] = semi.empty
{
import semiInstances._
// NOTE: These typecheck but crash the compiler on Scala 2.13
// println(semi.empty[IList[Int]])
// println(semi.empty[Snoc[Int]])
illTyped("semi.empty[Rgb]")
def run(): Unit = testEmpty("semi")
// println(semiauto.empty[IList[Int]])
// println(semiauto.empty[Snoc[Int]])
illTyped("semiauto.empty[Rgb]")
testEmpty("semiauto")
}
}

object EmptySuite {
import TestDefns._

// `Monoid[Option[A]]` gives us `Empty[Option[A]]` but it requires a `Semigroup[A]`.
implicit def emptyOption[A]: Empty[Option[A]] = Empty(None)

object semiInstances {
implicit val foo: Empty[Foo] = semiauto.empty
implicit val outer: Empty[Outer] = semiauto.empty
implicit val interleaved: Empty[Interleaved[String]] = semiauto.empty
implicit val recursive: Empty[Recursive] = semiauto.empty
implicit val iList: Empty[IList[Dummy]] = semiauto.empty
implicit val snoc: Empty[Snoc[Dummy]] = semiauto.empty
implicit val box: Empty[Box[Mask]] = semiauto.empty
implicit val chain: Empty[Chain] = semiauto.empty
}

trait Dummy
final case class Chain(head: Int, tail: Chain)
Expand Down
Loading

0 comments on commit 80bf8f8

Please sign in to comment.