diff --git a/core/src/main/scala/cats/data/Inverse.scala b/core/src/main/scala/cats/data/Inverse.scala new file mode 100644 index 0000000000..00a4c8beca --- /dev/null +++ b/core/src/main/scala/cats/data/Inverse.scala @@ -0,0 +1,123 @@ +/* + * 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 data + +import cats.kernel.LowerBounded +import cats.kernel.UpperBounded + +object InverseImpl extends InverseInstances with Newtype { + private[data] def unwrap[A](value: Type[A]): A = + value.asInstanceOf[A] + + private[data] def create[A](value: A): Type[A] = + value.asInstanceOf[Type[A]] + + def apply[A](value: A): Inverse[A] = + create(value) + + implicit def catsInverseOps[A](value: Inverse[A]): InverseOps[A] = + new InverseOps[A](value) +} + +sealed class InverseOps[A](val inverse: Inverse[A]) { + def value: A = InverseImpl.unwrap(inverse) +} + +sealed abstract private[data] class InverseInstances extends InverseInstances0 { + implicit def hashAndOrderForInverse[A: Hash: Order]: Hash[Inverse[A]] with Order[Inverse[A]] = + new Hash[Inverse[A]] with Order[Inverse[A]] { + override def hash(x: Inverse[A]): Int = + Hash[A].hash(x.value) + + override def compare(x: Inverse[A], y: Inverse[A]): Int = + Order.reverse[A](Order[A]).compare(x.value, y.value) + } + + implicit def orderingForInverseFromHashAndOrder[A](implicit A: Order[Inverse[A]]): Ordering[Inverse[A]] = + A.toOrdering + + implicit def lowerBoundForInverse[A](implicit + A: UpperBounded[A], + B: PartialOrder[Inverse[A]] + ): LowerBounded[Inverse[A]] = + new LowerBounded[Inverse[A]] { + override def partialOrder: PartialOrder[Inverse[A]] = B + + override def minBound: Inverse[A] = + Inverse(A.maxBound) + } + + implicit def upperBoundForInverse[A](implicit + A: LowerBounded[A], + B: PartialOrder[Inverse[A]] + ): UpperBounded[Inverse[A]] = + new UpperBounded[Inverse[A]] { + override def partialOrder: PartialOrder[Inverse[A]] = B + + override def maxBound: Inverse[A] = + Inverse(A.minBound) + } +} + +sealed private[data] trait InverseInstances0 extends InverseInstances1 { + implicit def hashAndPartialOrderForInverse[A: Hash: PartialOrder]: Hash[Inverse[A]] with PartialOrder[Inverse[A]] = + new Hash[Inverse[A]] with PartialOrder[Inverse[A]] { + override def hash(x: Inverse[A]): Int = + Hash[A].hash(x.value) + + override def partialCompare(x: Inverse[A], y: Inverse[A]): Double = + PartialOrder.reverse(PartialOrder[A]).partialCompare(x.value, y.value) + } +} + +sealed private[data] trait InverseInstances1 extends InverseInstances2 { + implicit def orderForInverse[A: Order]: Order[Inverse[A]] = + new Order[Inverse[A]] { + override def compare(x: Inverse[A], y: Inverse[A]): Int = + Order.reverse(Order[A]).compare(x.value, y.value) + } +} + +sealed private[data] trait InverseInstances2 extends InverseInstances3 { + implicit def partialOrderForInverse[A: PartialOrder]: PartialOrder[Inverse[A]] = + new PartialOrder[Inverse[A]] { + override def partialCompare(x: Inverse[A], y: Inverse[A]): Double = + PartialOrder.reverse(PartialOrder[A]).partialCompare(x.value, y.value) + } +} + +sealed private[data] trait InverseInstances3 extends InverseInstances4 { + implicit def hashForInverse[A: Hash]: Hash[Inverse[A]] = + new Hash[Inverse[A]] { + override def hash(x: Inverse[A]): Int = + Hash[A].hash(x.value) + + override def eqv(x: Inverse[A], y: Inverse[A]): Boolean = + Hash[A].eqv(x.value, y.value) + } +} + +sealed private[data] trait InverseInstances4 { + implicit def eqForInverse[A: Eq]: Eq[Inverse[A]] = + Eq.by[Inverse[A], A](_.value) +} diff --git a/core/src/main/scala/cats/data/package.scala b/core/src/main/scala/cats/data/package.scala index 4ac5961361..38e61342b2 100644 --- a/core/src/main/scala/cats/data/package.scala +++ b/core/src/main/scala/cats/data/package.scala @@ -41,6 +41,29 @@ package object data extends ScalaVersionSpecificPackage { type NonEmptyChain[+A] = NonEmptyChainImpl.Type[A] val NonEmptyChain = NonEmptyChainImpl + /** Inverse is a data type which exists merely to invert the `Order`, + * `PartialOrder`, `UpperBounded`, and `LowerBounded` instances for some + * type `A`. In some languages this type is called `Down`. + * + * {{{ + * scala> import cats.data._ + * import cats.data._ + * + * scala> import scala.collection.immutable.SortedSet + * import scala.collection.immutable.SortedSet + * + * scala> SortedSet(1, 2, 3) + * val res0: scala.collection.immutable.SortedSet[Int] = TreeSet(1, 2, 3) + * + * scala> res0.map(Inverse.apply) + * val res1: scala.collection.immutable.SortedSet[cats.data.Inverse[Int]] = TreeSet(3, 2, 1) + * }}} + * + * @see [[https://hackage.haskell.org/package/base-4.6.0.1/docs/Data-Ord.html#Down]] + */ + type Inverse[A] = InverseImpl.Type[A] + val Inverse = InverseImpl + type ReaderT[F[_], -A, B] = Kleisli[F, A, B] val ReaderT = Kleisli