Skip to content

Commit

Permalink
Support singleton instances (#612)
Browse files Browse the repository at this point in the history
* Add singleton instances for Scala 2

* Add singleton instances for Scala 3

* Use default arguments
  • Loading branch information
joroKr21 authored Oct 17, 2023
1 parent 73a48ac commit 8f442ad
Show file tree
Hide file tree
Showing 43 changed files with 390 additions and 282 deletions.
9 changes: 7 additions & 2 deletions core/src/main/scala-2/cats/derived/eq.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
package cats.derived

import cats.Eq
import cats.derived.util.VersionSpecific.{Lazy, OrElse}
import shapeless._
import util.VersionSpecific.{OrElse, Lazy}

import scala.annotation.implicitNotFound

Expand All @@ -32,7 +32,7 @@ object MkEq extends MkEqDerivation {
def apply[A](implicit ev: MkEq[A]): MkEq[A] = ev
}

abstract private[derived] class MkEqDerivation {
abstract private[derived] class MkEqDerivation extends MkEqSingletons {
implicit val mkEqHNil: MkEq[HNil] = (_, _) => true
implicit val mkEqCNil: MkEq[CNil] = (_, _) => true

Expand All @@ -49,3 +49,8 @@ abstract private[derived] class MkEqDerivation {
implicit def mkEqGeneric[A, R](implicit A: Generic.Aux[A, R], R: Lazy[MkEq[R]]): MkEq[A] =
(x, y) => R.value.eqv(A.to(x), A.to(y))
}

abstract private[derived] class MkEqSingletons {
implicit def mkEqSingleton[A: Witness.Aux]: MkEq[A] =
(_, _) => true
}
17 changes: 16 additions & 1 deletion core/src/main/scala-2/cats/derived/hash.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ private[derived] trait HashBuilder[A] extends Serializable {
}
}

import cats.derived.util.VersionSpecific.{Lazy, OrElse}
import shapeless._
import util.VersionSpecific.{OrElse, Lazy}

private[derived] object HashBuilder {

Expand All @@ -52,6 +52,17 @@ private[derived] object HashBuilder {
}

abstract private[derived] class MkHashDerivation extends MkHashGenericProduct {
// These instances support singleton types unlike the instances in Cats' kernel.
implicit def mkHashBoolean[A <: Boolean]: MkHash[A] = universal
implicit def mkHashByte[A <: Byte]: MkHash[A] = universal
implicit def mkHashShort[A <: Short]: MkHash[A] = universal
implicit def mkHashInt[A <: Int]: MkHash[A] = universal
implicit def mkHashLong[A <: Long]: MkHash[A] = universal
implicit def mkHashFloat[A <: Float]: MkHash[A] = universal
implicit def mkHashDouble[A <: Double]: MkHash[A] = universal
implicit def mkHashChar[A <: Char]: MkHash[A] = universal
implicit def mkHashString[A <: String]: MkHash[A] = universal
implicit def mkHashSymbol[A <: Symbol]: MkHash[A] = universal

implicit val mkHashCNil: MkHash[CNil] =
instance(_ => 0, (_, _) => true)
Expand Down Expand Up @@ -99,6 +110,7 @@ abstract private[derived] class MkHashGenericHList extends MkHashGenericCoproduc
}

abstract private[derived] class MkHashGenericCoproduct {
private[this] val universalInstance = instance[Any](_.hashCode, _ == _)

implicit def mkHashGenericCoproduct[A, R <: Coproduct](implicit
A: Generic.Aux[A, R],
Expand All @@ -108,6 +120,9 @@ abstract private[derived] class MkHashGenericCoproduct {
(x, y) => R.value.eqv(A.to(x), A.to(y))
)

protected def universal[A]: MkHash[A] =
universalInstance.asInstanceOf[MkHash[A]]

protected def instance[A](f: A => Int, g: (A, A) => Boolean): MkHash[A] =
new MkHash[A] {
def hash(x: A) = f(x)
Expand Down
11 changes: 8 additions & 3 deletions core/src/main/scala-2/cats/derived/order.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless rorduired by applicable law or agreed to in writing, software
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
Expand All @@ -17,8 +17,8 @@
package cats.derived

import cats.Order
import cats.derived.util.VersionSpecific.{Lazy, OrElse}
import shapeless._
import util.VersionSpecific.{OrElse, Lazy}

import scala.annotation.implicitNotFound

Expand All @@ -32,7 +32,7 @@ object MkOrder extends MkOrderDerivation {
def apply[A](implicit ev: MkOrder[A]): MkOrder[A] = ev
}

abstract private[derived] class MkOrderDerivation {
abstract private[derived] class MkOrderDerivation extends MkOrderSingletons {
implicit val mkOrderHNil: MkOrder[HNil] = (_, _) => 0
implicit val mkOrderCNil: MkOrder[CNil] = (_, _) => 0

Expand All @@ -52,3 +52,8 @@ abstract private[derived] class MkOrderDerivation {
implicit def mkOrderGeneric[A, R](implicit A: Generic.Aux[A, R], R: Lazy[MkOrder[R]]): MkOrder[A] =
(x, y) => R.value.compare(A.to(x), A.to(y))
}

abstract private[derived] class MkOrderSingletons {
implicit def mkOrderSingleton[A: Witness.Aux]: MkOrder[A] =
(_, _) => 0
}
11 changes: 8 additions & 3 deletions core/src/main/scala-2/cats/derived/partialOrder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless rorduired by applicable law or agreed to in writing, software
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
Expand All @@ -17,8 +17,8 @@
package cats.derived

import cats.PartialOrder
import cats.derived.util.VersionSpecific.{Lazy, OrElse}
import shapeless._
import util.VersionSpecific.{OrElse, Lazy}

import scala.annotation.implicitNotFound

Expand All @@ -32,7 +32,7 @@ object MkPartialOrder extends MkPartialOrderDerivation {
def apply[A](implicit ev: MkPartialOrder[A]): MkPartialOrder[A] = ev
}

abstract private[derived] class MkPartialOrderDerivation {
abstract private[derived] class MkPartialOrderDerivation extends MkPartialOrderSingletons {
implicit val mkPartialOrderHNil: MkPartialOrder[HNil] = (_, _) => 0
implicit val mkPartialOrderCNil: MkPartialOrder[CNil] = (_, _) => 0

Expand All @@ -58,3 +58,8 @@ abstract private[derived] class MkPartialOrderDerivation {
R: Lazy[MkPartialOrder[R]]
): MkPartialOrder[A] = (x, y) => R.value.partialCompare(A.to(x), A.to(y))
}

abstract private[derived] class MkPartialOrderSingletons {
implicit def mkPartialOrderSingleton[A: Witness.Aux]: MkPartialOrder[A] =
(_, _) => 0
}
7 changes: 6 additions & 1 deletion core/src/main/scala-2/cats/derived/pure.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
package cats.derived

import alleycats.{Empty, Pure}
import cats.derived.util.VersionSpecific.OrElse
import shapeless._
import util.VersionSpecific.OrElse

import scala.annotation.implicitNotFound

Expand Down Expand Up @@ -50,6 +50,11 @@ abstract private[derived] class MkPureDerivation extends MkPureNested {

abstract private[derived] class MkPureNested extends MkPureCons {

implicit def mkPureSingleton[T](implicit T: Witness.Aux[T]): MkPure[Const[T]#λ] =
new MkPure[Const[T]#λ] {
def pure[A](a: A) = T.value
}

implicit def mkPureNested[F[_]](implicit F: Split1[F, PureOrMk, PureOrMk]): MkPure[F] =
new MkPure[F] {
def pure[A](a: A) = F.pack(F.fo.unify.pure(F.fi.unify.pure(a)))
Expand Down
5 changes: 3 additions & 2 deletions core/src/main/scala-2/cats/derived/show.scala
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package cats
package derived

import cats.Show.ContravariantShow
import cats.derived.util.VersionSpecific.{Lazy, OrElse}
import shapeless._
import shapeless.labelled._
import util.VersionSpecific.{Lazy, OrElse}

import scala.annotation.implicitNotFound
import scala.reflect.ClassTag
Expand Down Expand Up @@ -31,7 +32,7 @@ sealed abstract private[derived] class MkShowDerivation extends MkShowGenericCop

implicit def mkShowLabelledHCons[K <: Symbol, V, T <: HList](implicit
K: Witness.Aux[K],
V: Show[V] OrElse MkShow[V],
V: ContravariantShow[V] OrElse MkShow[V],
T: MkShow[T]
): MkShow[FieldType[K, V] :: T] = { case v :: t =>
val name = K.value.name
Expand Down
5 changes: 3 additions & 2 deletions core/src/main/scala-2/cats/derived/showPretty.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cats.derived

import cats.Show
import cats.Show.ContravariantShow
import cats.derived.util.VersionSpecific.{Lazy, OrElse}
import shapeless._
import shapeless.labelled._
Expand Down Expand Up @@ -33,7 +34,7 @@ sealed abstract private[derived] class MkShowPrettyDerivation extends MkShowPret

implicit def mkShowPrettyLabelledHCons[K <: Symbol, V, T <: HList](implicit
K: Witness.Aux[K],
V: Show[V] OrElse MkShowPretty[V],
V: ContravariantShow[V] OrElse MkShowPretty[V],
T: MkShowPretty[T]
): MkShowPretty[FieldType[K, V] :: T] = { case v :: t =>
val name = K.value.name
Expand Down Expand Up @@ -71,7 +72,7 @@ sealed abstract private[derived] class MkShowPrettyDerivation extends MkShowPret

sealed abstract private[derived] class MkShowPrettyGenericCoproduct {

protected def mkShowLines[A](show: Show[A] OrElse MkShowPretty[A])(a: A): List[String] =
protected def mkShowLines[A](show: ContravariantShow[A] OrElse MkShowPretty[A])(a: A): List[String] =
show.fold(
{
case pretty: ShowPretty[A] => pretty.showLines(a)
Expand Down
9 changes: 5 additions & 4 deletions core/src/main/scala-3/cats/derived/DerivedEq.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ object DerivedEq:
import DerivedEq.given
summonInline[DerivedEq[A]].instance

given singleton[A <: Singleton: ValueOf]: DerivedEq[A] =
Eq.allEqual

given product[A](using inst: => K0.ProductInstances[Or, A]): DerivedEq[A] =
given K0.ProductInstances[Eq, A] = inst.unify
new Product[Eq, A] {}
Expand All @@ -26,11 +29,9 @@ object DerivedEq:
new Coproduct[Eq, A] {}

trait Product[F[x] <: Eq[x], A](using inst: K0.ProductInstances[F, A]) extends Eq[A]:
final override def eqv(x: A, y: A): Boolean = inst.foldLeft2(x, y)(true: Boolean)(
final override def eqv(x: A, y: A): Boolean = inst.foldLeft2(x, y)(true: Boolean):
[t] => (acc: Boolean, eqt: F[t], x: t, y: t) => Complete(!eqt.eqv(x, y))(false)(true)
)

trait Coproduct[F[x] <: Eq[x], A](using inst: K0.CoproductInstances[F, A]) extends Eq[A]:
final override def eqv(x: A, y: A): Boolean = inst.fold2(x, y)(false)(
final override def eqv(x: A, y: A): Boolean = inst.fold2(x, y)(false):
[t] => (eqt: F[t], x: t, y: t) => eqt.eqv(x, y)
)
21 changes: 15 additions & 6 deletions core/src/main/scala-3/cats/derived/DerivedHash.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,18 @@ object DerivedHash:
import DerivedHash.given
summonInline[DerivedHash[A]].instance

// These instances support singleton types unlike the instances in Cats' kernel.
given boolean[A <: Boolean]: DerivedHash[A] = Hash.fromUniversalHashCode
given byte[A <: Byte]: DerivedHash[A] = Hash.fromUniversalHashCode
given short[A <: Short]: DerivedHash[A] = Hash.fromUniversalHashCode
given int[A <: Int]: DerivedHash[A] = Hash.fromUniversalHashCode
given long[A <: Long]: DerivedHash[A] = Hash.fromUniversalHashCode
given float[A <: Float]: DerivedHash[A] = Hash.fromUniversalHashCode
given double[A <: Double]: DerivedHash[A] = Hash.fromUniversalHashCode
given char[A <: Char]: DerivedHash[A] = Hash.fromUniversalHashCode
given string[A <: String]: DerivedHash[A] = Hash.fromUniversalHashCode
given symbol[A <: Symbol]: DerivedHash[A] = Hash.fromUniversalHashCode

given product[A <: scala.Product](using inst: => K0.ProductInstances[Or, A]): DerivedHash[A] =
given K0.ProductInstances[Hash, A] = inst.unify
new Product[Hash, A] {}
Expand All @@ -35,12 +47,9 @@ object DerivedHash:
val prefix = x.productPrefix.hashCode
if arity <= 0 then prefix
else
MurmurHash3.finalizeHash(
inst.foldLeft[Int](x)(MurmurHash3.mix(MurmurHash3.productSeed, prefix))(
[t] => (acc: Int, h: F[t], x: t) => Continue(MurmurHash3.mix(acc, h.hash(x)))
),
arity
)
val hash = inst.foldLeft[Int](x)(MurmurHash3.mix(MurmurHash3.productSeed, prefix)):
[t] => (acc: Int, h: F[t], x: t) => Continue(MurmurHash3.mix(acc, h.hash(x)))
MurmurHash3.finalizeHash(hash, arity)

trait Coproduct[F[x] <: Hash[x], A](using inst: K0.CoproductInstances[F, A])
extends DerivedEq.Coproduct[F, A],
Expand Down
16 changes: 7 additions & 9 deletions core/src/main/scala-3/cats/derived/DerivedOrder.scala
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
package cats.derived

import cats.{Order, Show}
import shapeless3.deriving.{Complete, Continue, K0, Labelling}
import cats.Order
import shapeless3.deriving.{Complete, K0}

import scala.annotation.implicitNotFound
import scala.compiletime.*
import scala.deriving.Mirror

@implicitNotFound("""Could not derive an instance of Order[A] where A = ${A}.
Make sure that A satisfies one of the following conditions:
Expand All @@ -19,6 +18,9 @@ object DerivedOrder:
import DerivedOrder.given
summonInline[DerivedOrder[A]].instance

given singleton[A <: Singleton: ValueOf]: DerivedOrder[A] =
Order.allEqual

given product[A](using inst: => K0.ProductInstances[Or, A]): DerivedOrder[A] =
given K0.ProductInstances[Order, A] = inst.unify
new Product[Order, A] {}
Expand All @@ -28,18 +30,14 @@ object DerivedOrder:
new Coproduct[Order, A] {}

trait Product[T[x] <: Order[x], A](using inst: K0.ProductInstances[T, A]) extends Order[A]:

def compare(x: A, y: A): Int =
inst.foldLeft2(x, y)(0: Int)(
inst.foldLeft2(x, y)(0: Int):
[t] =>
(acc: Int, ord: T[t], t0: t, t1: t) =>
val cmp = ord.compare(t0, t1)
Complete(cmp != 0)(cmp)(acc)
)

trait Coproduct[T[x] <: Order[x], A](using inst: K0.CoproductInstances[T, A]) extends Order[A]:

def compare(x: A, y: A): Int =
inst.fold2(x, y)((x: Int, y: Int) => x - y)(
inst.fold2(x, y)((x: Int, y: Int) => x - y):
[t] => (ord: T[t], t0: t, t1: t) => ord.compare(t0, t1)
)
16 changes: 7 additions & 9 deletions core/src/main/scala-3/cats/derived/DerivedPartialOrder.scala
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
package cats.derived

import cats.{PartialOrder, Show}
import shapeless3.deriving.{Complete, Continue, K0, Labelling}
import cats.{Order, PartialOrder}
import shapeless3.deriving.{Complete, K0}

import scala.annotation.implicitNotFound
import scala.compiletime.*
import scala.deriving.Mirror

@implicitNotFound("""Could not derive an instance of PartialOrder[A] where A = ${A}.
Make sure that A satisfies one of the following conditions:
Expand All @@ -19,6 +18,9 @@ object DerivedPartialOrder:
import DerivedPartialOrder.given
summonInline[DerivedPartialOrder[A]].instance

given singleton[A <: Singleton: ValueOf]: DerivedPartialOrder[A] =
Order.allEqual

given product[A](using inst: => K0.ProductInstances[Or, A]): DerivedPartialOrder[A] =
given K0.ProductInstances[PartialOrder, A] = inst.unify
new Product[PartialOrder, A] {}
Expand All @@ -28,18 +30,14 @@ object DerivedPartialOrder:
new Coproduct[PartialOrder, A] {}

trait Product[T[x] <: PartialOrder[x], A](using inst: K0.ProductInstances[T, A]) extends PartialOrder[A]:

def partialCompare(x: A, y: A): Double =
inst.foldLeft2(x, y)(0: Double)(
inst.foldLeft2(x, y)(0: Double):
[t] =>
(acc: Double, ord: T[t], t0: t, t1: t) =>
val cmp = ord.partialCompare(t0, t1)
Complete(cmp != 0)(cmp)(acc)
)

trait Coproduct[T[x] <: PartialOrder[x], A](using inst: K0.CoproductInstances[T, A]) extends PartialOrder[A]:

def partialCompare(x: A, y: A): Double =
inst.fold2(x, y)(Double.NaN: Double)(
inst.fold2(x, y)(Double.NaN: Double):
[t] => (ord: T[t], t0: t, t1: t) => ord.partialCompare(t0, t1)
)
4 changes: 4 additions & 0 deletions core/src/main/scala-3/cats/derived/DerivedPure.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ object DerivedPure:
new Pure[Const[T]]:
def pure[A](a: A) = T.empty

given [T <: Singleton: ValueOf]: DerivedPure[Const[T]] =
new Pure[Const[T]]:
def pure[A](a: A) = valueOf[T]

given [F[_], G[_]](using F: Or[F], G: Or[G]): DerivedPure[[x] =>> F[G[x]]] =
new Pure[[x] =>> F[G[x]]]:
def pure[A](a: A) = F.unify.pure(G.unify.pure(a))
Expand Down
Loading

0 comments on commit 8f442ad

Please sign in to comment.