Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Defer instances for Eq, Hash, Order, Show, and variants #4414

Merged
merged 10 commits into from
Jul 10, 2023
1 change: 1 addition & 0 deletions core/src/main/scala-2.12/cats/instances/all.scala
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ trait AllInstances
with PartialOrderingInstances
with QueueInstances
with SetInstances
with ShowInstances
with SortedMapInstances
with SortedSetInstances
with StreamInstances
Expand Down
1 change: 1 addition & 0 deletions core/src/main/scala-2.12/cats/instances/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ package object instances {
object set extends SetInstances
object seq extends SeqInstances
object short extends ShortInstances
object show extends ShowInstances
object sortedMap
extends SortedMapInstances
with SortedMapInstancesBinCompat0
Expand Down
1 change: 1 addition & 0 deletions core/src/main/scala-2.13+/cats/instances/all.scala
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ trait AllInstances
with SetInstances
with SortedMapInstances
with SortedSetInstances
with ShowInstances
with StreamInstances
with StringInstances
with SymbolInstances
Expand Down
1 change: 1 addition & 0 deletions core/src/main/scala-2.13+/cats/instances/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ package object instances {
object set extends SetInstances
object seq extends SeqInstances
object short extends ShortInstances
object show extends ShowInstances
object sortedMap
extends SortedMapInstances
with SortedMapInstancesBinCompat0
Expand Down
9 changes: 9 additions & 0 deletions core/src/main/scala/cats/Defer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,16 @@ trait Defer[F[_]] extends Serializable {
object Defer {
def apply[F[_]](implicit defer: Defer[F]): Defer[F] = defer

implicit def catsDeferForEq: Defer[Eq] = cats.implicits.catsDeferForEq
implicit def catsDeferForEquiv: Defer[Equiv] = cats.implicits.catsDeferForEquiv
implicit def catsDeferForFunction0: Defer[Function0] = cats.instances.function.catsSddDeferForFunction0
implicit def catsDeferForFunction1[A]: Defer[Function1[A, *]] = cats.instances.function.catsStdDeferForFunction1[A]
implicit def catsDeferForHash: Defer[Hash] = cats.implicits.catsDeferForHash
implicit def catsDeferForOrder: Defer[Order] = cats.instances.order.catsDeferForOrder
implicit def catsStdDeferForOrdering: Defer[Ordering] = cats.instances.ordering.catsStdDeferForOrdering
implicit def catsDeferForPartialOrder: Defer[PartialOrder] = cats.instances.partialOrder.catsDeferForPartialOrder
implicit def catsStdDeferForPartialOrdering: Defer[PartialOrdering] =
cats.instances.partialOrdering.catsStdDeferForPartialOrdering
implicit def catsDeferForShow: Defer[Show] = cats.implicits.catsDeferForShow
implicit def catsDeferForTailRec: Defer[TailRec] = cats.instances.tailRec.catsInstancesForTailRec
}
23 changes: 23 additions & 0 deletions core/src/main/scala/cats/instances/eq.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
package cats
package instances

import scala.annotation.tailrec

trait EqInstances extends kernel.instances.EqInstances {
implicit val catsContravariantMonoidalForEq: ContravariantMonoidal[Eq] =
new ContravariantMonoidal[Eq] {
Expand All @@ -43,4 +45,25 @@ trait EqInstances extends kernel.instances.EqInstances {
def product[A, B](fa: Eq[A], fb: Eq[B]): Eq[(A, B)] =
(left, right) => fa.eqv(left._1, right._1) && fb.eqv(left._2, right._2)
}

implicit val catsDeferForEq: Defer[Eq] =
morgen-peschke marked this conversation as resolved.
Show resolved Hide resolved
new Defer[Eq] {
case class Deferred[A](fa: () => Eq[A]) extends Eq[A] {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a stylish nitpick that perhaps doesn't affect anything. I'm not confident about putting underlying classes to the value instantiation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't usually use this style, and went with it to remain consistent with the existing instances (like cats.instances.FunctionInstancesBinCompat0#catsSddDeferForFunction0).

As I usually avoid it for stylistic reasons, I'm not familiar with the practical concerns around doing it this way, so I'd be interested in hearing why you're worried about it.

override def eqv(x: A, y: A): Boolean = {
@tailrec
def loop(f: () => Eq[A]): Boolean =
morgen-peschke marked this conversation as resolved.
Show resolved Hide resolved
f() match {
case Deferred(f) => loop(f)
case next => next.eqv(x, y)
}

loop(fa)
}
}

override def defer[A](fa: => Eq[A]): Eq[A] = {
lazy val cachedFa = fa
Deferred(() => cachedFa)
}
}
}
23 changes: 23 additions & 0 deletions core/src/main/scala/cats/instances/equiv.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
package cats
package instances

import scala.annotation.tailrec

trait EquivInstances {
implicit val catsContravariantMonoidalForEquiv: ContravariantMonoidal[Equiv] =
new ContravariantMonoidal[Equiv] {
Expand Down Expand Up @@ -52,4 +54,25 @@ trait EquivInstances {
fa.equiv(l._1, r._1) && fb.equiv(l._2, r._2)
}
}

implicit val catsDeferForEquiv: Defer[Equiv] =
new Defer[Equiv] {
case class Deferred[A](fa: () => Equiv[A]) extends Equiv[A] {
override def equiv(x: A, y: A): Boolean = {
@tailrec
def loop(f: () => Equiv[A]): Boolean =
f() match {
case Deferred(f) => loop(f)
case next => next.equiv(x, y)
}

loop(fa)
}
}

override def defer[A](fa: => Equiv[A]): Equiv[A] = {
lazy val cachedFa = fa
Deferred(() => cachedFa)
}
}
}
29 changes: 29 additions & 0 deletions core/src/main/scala/cats/instances/hash.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
package cats
package instances

import scala.annotation.tailrec

trait HashInstances extends kernel.instances.HashInstances {

implicit val catsContravariantForHash: Contravariant[Hash] =
Expand All @@ -34,4 +36,31 @@ trait HashInstances extends kernel.instances.HashInstances {

}

implicit val catsDeferForHash: Defer[Hash] =
new Defer[Hash] {
case class Deferred[A](fa: () => Hash[A]) extends Hash[A] {
private lazy val resolve: Hash[A] = {
@tailrec
def loop(f: () => Hash[A]): Hash[A] =
f() match {
case Deferred(f) => loop(f)
case next => next
}

loop(fa)
}

override def hash(x: A): Int = resolve.hash(x)

/**
* Returns `true` if `x` and `y` are equivalent, `false` otherwise.
*/
override def eqv(x: A, y: A): Boolean = resolve.eqv(x, y)
}

override def defer[A](fa: => Hash[A]): Hash[A] = {
lazy val cachedFa = fa
Deferred(() => cachedFa)
}
}
}
23 changes: 23 additions & 0 deletions core/src/main/scala/cats/instances/order.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ package instances

import cats.kernel.instances.unit._

import scala.annotation.tailrec

trait OrderInstances extends kernel.instances.OrderInstances {

implicit val catsContravariantMonoidalForOrder: ContravariantMonoidal[Order] =
Expand All @@ -47,4 +49,25 @@ trait OrderInstances extends kernel.instances.OrderInstances {
if (z == 0) fb.compare(x._2, y._2) else z
}
}

implicit val catsDeferForOrder: Defer[Order] =
new Defer[Order] {
case class Deferred[A](fa: () => Order[A]) extends Order[A] {
override def compare(x: A, y: A): Int = {
@tailrec
def loop(f: () => Order[A]): Int =
f() match {
case Deferred(f) => loop(f)
case next => next.compare(x, y)
}

loop(fa)
}
}

override def defer[A](fa: => Order[A]): Order[A] = {
lazy val cachedFa = fa
Deferred(() => cachedFa)
}
}
}
23 changes: 23 additions & 0 deletions core/src/main/scala/cats/instances/ordering.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ package instances

import cats.kernel.instances.unit._

import scala.annotation.tailrec

trait OrderingInstances {
implicit val catsContravariantMonoidalForOrdering: ContravariantMonoidal[Ordering] =
new ContravariantMonoidal[Ordering] {
Expand All @@ -43,4 +45,25 @@ trait OrderingInstances {
}
}
}

implicit val catsStdDeferForOrdering: Defer[Ordering] =
new Defer[Ordering] {
case class Deferred[A](fa: () => Ordering[A]) extends Ordering[A] {
override def compare(x: A, y: A): Int = {
@tailrec
def loop(f: () => Ordering[A]): Int =
f() match {
case Deferred(f) => loop(f)
case next => next.compare(x, y)
}

loop(fa)
}
}

override def defer[A](fa: => Ordering[A]): Ordering[A] = {
lazy val cachedFa = fa
Deferred(() => cachedFa)
}
}
}
23 changes: 23 additions & 0 deletions core/src/main/scala/cats/instances/partialOrder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ package cats
package instances
import cats.kernel.instances.unit._

import scala.annotation.tailrec

trait PartialOrderInstances extends kernel.instances.PartialOrderInstances {
implicit val catsContravariantMonoidalForPartialOrder: ContravariantMonoidal[PartialOrder] =
new ContravariantMonoidal[PartialOrder] {
Expand All @@ -41,4 +43,25 @@ trait PartialOrderInstances extends kernel.instances.PartialOrderInstances {

def unit: PartialOrder[Unit] = Order[Unit]
}

implicit val catsDeferForPartialOrder: Defer[PartialOrder] =
new Defer[PartialOrder] {
case class Deferred[A](fa: () => PartialOrder[A]) extends PartialOrder[A] {
override def partialCompare(x: A, y: A): Double = {
@tailrec
def loop(f: () => PartialOrder[A]): Double =
f() match {
case Deferred(f) => loop(f)
case next => next.partialCompare(x, y)
}

loop(fa)
}
}

override def defer[A](fa: => PartialOrder[A]): PartialOrder[A] = {
lazy val cachedFa = fa
Deferred(() => cachedFa)
}
}
}
27 changes: 27 additions & 0 deletions core/src/main/scala/cats/instances/partialOrdering.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
package cats
package instances

import scala.annotation.tailrec

trait PartialOrderingInstances {
implicit val catsContravariantMonoidalForPartialOrdering: ContravariantMonoidal[PartialOrdering] =
new ContravariantMonoidal[PartialOrdering] {
Expand Down Expand Up @@ -50,4 +52,29 @@ trait PartialOrderingInstances {

def unit: PartialOrdering[Unit] = cats.instances.unit.catsKernelStdOrderForUnit.toOrdering
}

implicit val catsStdDeferForPartialOrdering: Defer[PartialOrdering] =
new Defer[PartialOrdering] {
case class Deferred[A](fa: () => PartialOrdering[A]) extends PartialOrdering[A] {
private lazy val resolve: PartialOrdering[A] = {
@tailrec
def loop(f: () => PartialOrdering[A]): PartialOrdering[A] =
f() match {
case Deferred(f) => loop(f)
case next => next
}

loop(fa)
}

override def tryCompare(x: A, y: A): Option[Int] = resolve.tryCompare(x, y)

override def lteq(x: A, y: A): Boolean = resolve.lteq(x, y)
}

override def defer[A](fa: => PartialOrdering[A]): PartialOrdering[A] = {
lazy val cachedFa = fa
Deferred(() => cachedFa)
}
}
}
48 changes: 48 additions & 0 deletions core/src/main/scala/cats/instances/show.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright (c) 2015 Typelevel
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

package cats
package instances

import scala.annotation.tailrec

trait ShowInstances {
implicit val catsDeferForShow: Defer[Show] =
new Defer[Show] {
case class Deferred[A](fa: () => Show[A]) extends Show[A] {
override def show(t: A): String = {
@tailrec
def loop(f: () => Show[A]): String =
f() match {
case Deferred(f) => loop(f)
case next => next.show(t)
}

loop(fa)
}
}

override def defer[A](fa: => Show[A]): Show[A] = {
lazy val cachedFa = fa
Deferred(() => cachedFa)
}
}
}
4 changes: 2 additions & 2 deletions tests/shared/src/test/scala/cats/tests/EqSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ package cats.tests
import cats.{Contravariant, ContravariantMonoidal, ContravariantSemigroupal, Invariant, Semigroupal}
import cats.kernel.Eq
import cats.kernel.laws.discipline.SerializableTests
import cats.laws.discipline.{ContravariantMonoidalTests, MiniInt}
import cats.laws.discipline.{ContravariantMonoidalTests, DeferTests, MiniInt}
import cats.laws.discipline.arbitrary._
import cats.laws.discipline.eq._

Expand All @@ -36,5 +36,5 @@ class EqSuite extends CatsSuite {

checkAll("Eq", ContravariantMonoidalTests[Eq].contravariantMonoidal[MiniInt, Boolean, Boolean])
checkAll("ContravariantMonoidal[Eq]", SerializableTests.serializable(ContravariantMonoidal[Eq]))

checkAll("Defer[Eq]", DeferTests[Eq].defer[MiniInt])
}
1 change: 1 addition & 0 deletions tests/shared/src/test/scala/cats/tests/EquivSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,5 @@ class EquivSuite extends CatsSuite {
ContravariantMonoidalTests[Equiv].contravariantMonoidal[MiniInt, Boolean, Boolean]
)
checkAll("ContravariantMonoidal[Equiv]", SerializableTests.serializable(ContravariantMonoidal[Equiv]))
checkAll("Defer[Equiv]", DeferTests[Equiv].defer[MiniInt])
}
5 changes: 4 additions & 1 deletion tests/shared/src/test/scala/cats/tests/HashSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ package cats.tests

import cats.{Contravariant, Invariant}
import cats.kernel.Hash
import cats.laws.discipline.{DeferTests, MiniInt}
import cats.laws.discipline.arbitrary._
import cats.laws.discipline.eq._
import cats.syntax.hash._

class HashSuite extends CatsSuite {
Expand All @@ -34,5 +37,5 @@ class HashSuite extends CatsSuite {

assert(1.hash == 1.hashCode)
assert("ABC".hash == "ABC".hashCode)

checkAll("Defer[Equiv]", DeferTests[Hash].defer[MiniInt])
morgen-peschke marked this conversation as resolved.
Show resolved Hide resolved
}
Loading