Skip to content

Commit 41b99ed

Browse files
authored
Merge pull request #3918 from armanbilge/migrate-algebra-bincompat
Binary-compatibly move typelevel/algebra into cats repo
2 parents b3f95b2 + 84c780a commit 41b99ed

File tree

79 files changed

+4920
-15
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

79 files changed

+4920
-15
lines changed

README.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ functionality, you can pick-and-choose from amongst these modules
103103
* `cats-laws`: Laws for testing type class instances.
104104
* `cats-free`: Free structures such as the free monad, and supporting type classes.
105105
* `cats-testkit`: lib for writing tests for type class instances using laws.
106+
* `algebra`: Type classes to represent algebraic structures.
106107
* `alleycats-core`: Cats instances and classes which are not lawful.
107108

108109
There are several other Cats modules that are in separate repos so that they can
@@ -126,10 +127,11 @@ Links:
126127
2. ScalaDoc: [typelevel.org/cats/api/](https://typelevel.org/cats/api/)
127128
3. Type classes: [typelevel.org/cats/typeclasses](https://typelevel.org/cats/typeclasses.html)
128129
4. Data types: [typelevel.org/cats/datatypes.html](https://typelevel.org/cats/datatypes.html)
129-
5. Glossary: [typelevel.org/cats/nomenclature.html](https://typelevel.org/cats/nomenclature.html)
130-
6. Resources for Learners: [typelevel.org/cats/resources_for_learners.html](https://typelevel.org/cats/resources_for_learners.html)
131-
7. FAQ: [typelevel.org/cats/faq.html](https://typelevel.org/cats/faq.html)
132-
8. The Typelevel Ecosystem: [typelevel.org/cats/typelevelEcosystem.html](https://typelevel.org/cats/typelevelEcosystem.html)
130+
5. Algebra overview: [typelevel.org/cats/algebra.html](https://typelevel.org/cats/algebra.html)
131+
6. Glossary: [typelevel.org/cats/nomenclature.html](https://typelevel.org/cats/nomenclature.html)
132+
7. Resources for Learners: [typelevel.org/cats/resources_for_learners.html](https://typelevel.org/cats/resources_for_learners.html)
133+
8. FAQ: [typelevel.org/cats/faq.html](https://typelevel.org/cats/faq.html)
134+
9. The Typelevel Ecosystem: [typelevel.org/cats/typelevelEcosystem.html](https://typelevel.org/cats/typelevelEcosystem.html)
133135

134136
### Community
135137

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package algebra
2+
3+
import scala.annotation.nowarn
4+
5+
/**
6+
* Priority is a type class for prioritized implicit search.
7+
*
8+
* This type class will attempt to provide an implicit instance of `P`
9+
* (the preferred type). If that type is not available it will
10+
* fallback to `F` (the fallback type). If neither type is available
11+
* then a `Priority[P, F]` instance will not be available.
12+
*
13+
* This type can be useful for problems where multiple algorithms can
14+
* be used, depending on the type classes available.
15+
*/
16+
sealed trait Priority[+P, +F] {
17+
18+
import Priority.{Fallback, Preferred}
19+
20+
def fold[B](f1: P => B)(f2: F => B): B =
21+
this match {
22+
case Preferred(x) => f1(x)
23+
case Fallback(y) => f2(y)
24+
}
25+
26+
def join[U >: P with F]: U =
27+
fold(_.asInstanceOf[U])(_.asInstanceOf[U])
28+
29+
def bimap[P2, F2](f1: P => P2)(f2: F => F2): Priority[P2, F2] =
30+
this match {
31+
case Preferred(x) => Preferred(f1(x))
32+
case Fallback(y) => Fallback(f2(y))
33+
}
34+
35+
def toEither: Either[P, F] =
36+
fold[Either[P, F]](p => Left(p))(f => Right(f))
37+
38+
def isPreferred: Boolean =
39+
fold(_ => true)(_ => false)
40+
41+
def isFallback: Boolean =
42+
fold(_ => false)(_ => true)
43+
44+
def getPreferred: Option[P] =
45+
fold[Option[P]](p => Some(p))(_ => None)
46+
47+
def getFallback: Option[F] =
48+
fold[Option[F]](_ => None)(f => Some(f))
49+
}
50+
51+
object Priority extends FindPreferred {
52+
53+
case class Preferred[P](get: P) extends Priority[P, Nothing]
54+
case class Fallback[F](get: F) extends Priority[Nothing, F]
55+
56+
def apply[P, F](implicit ev: Priority[P, F]): Priority[P, F] = ev
57+
}
58+
59+
private[algebra] trait FindPreferred extends FindFallback {
60+
@nowarn("msg=deprecated")
61+
implicit def preferred[P](implicit ev: P): Priority[P, Nothing] =
62+
Priority.Preferred(ev)
63+
}
64+
65+
private[algebra] trait FindFallback {
66+
@nowarn("msg=deprecated")
67+
implicit def fallback[F](implicit ev: F): Priority[Nothing, F] =
68+
Priority.Fallback(ev)
69+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package algebra.instances
2+
3+
import scala.annotation.tailrec
4+
5+
object StaticMethods {
6+
7+
/**
8+
* Exponentiation function, e.g. x^y
9+
*
10+
* If base^ex doesn't fit in a Long, the result will overflow (unlike
11+
* Math.pow which will return +/- Infinity).
12+
*/
13+
final def pow(base: Long, exponent: Long): Long = {
14+
@tailrec def loop(t: Long, b: Long, e: Long): Long =
15+
if (e == 0L) t
16+
else if ((e & 1) == 1) loop(t * b, b * b, e >>> 1L)
17+
else loop(t, b * b, e >>> 1L)
18+
19+
if (exponent >= 0L) loop(1L, base, exponent)
20+
else {
21+
if (base == 0L) throw new ArithmeticException("zero can't be raised to negative power")
22+
else if (base == 1L) 1L
23+
else if (base == -1L) if ((exponent & 1L) == 0L) -1L else 1L
24+
else 0L
25+
}
26+
}
27+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package algebra
2+
package instances
3+
4+
package object all extends AllInstances
5+
6+
trait AllInstances
7+
extends ArrayInstances
8+
with BigDecimalInstances
9+
with BigIntInstances
10+
with BitSetInstances
11+
with BooleanInstances
12+
with ByteInstances
13+
with CharInstances
14+
with DoubleInstances
15+
with FloatInstances
16+
with IntInstances
17+
with ListInstances
18+
with LongInstances
19+
with MapInstances
20+
with OptionInstances
21+
with SetInstances
22+
with ShortInstances
23+
with StringInstances
24+
with TupleInstances
25+
with UnitInstances
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package algebra
2+
package instances
3+
4+
import scala.{specialized => sp}
5+
6+
package object array extends ArrayInstances
7+
8+
trait ArrayInstances {
9+
implicit def arrayEq[@sp A: Eq]: Eq[Array[A]] =
10+
new ArrayEq[A]
11+
implicit def arrayOrder[@sp A: Order]: Order[Array[A]] =
12+
new ArrayOrder[A]
13+
implicit def arrayPartialOrder[@sp A: PartialOrder]: PartialOrder[Array[A]] =
14+
new ArrayPartialOrder[A]
15+
}
16+
17+
private object ArraySupport {
18+
19+
private def signum(x: Int): Int =
20+
if (x < 0) -1
21+
else if (x > 0) 1
22+
else 0
23+
24+
def eqv[@sp A](x: Array[A], y: Array[A])(implicit ev: Eq[A]): Boolean = {
25+
var i = 0
26+
if (x.length != y.length) return false
27+
while (i < x.length && i < y.length && ev.eqv(x(i), y(i))) i += 1
28+
i == x.length
29+
}
30+
31+
def compare[@sp A](x: Array[A], y: Array[A])(implicit ev: Order[A]): Int = {
32+
var i = 0
33+
while (i < x.length && i < y.length) {
34+
val cmp = ev.compare(x(i), y(i))
35+
if (cmp != 0) return cmp
36+
i += 1
37+
}
38+
signum(x.length - y.length)
39+
}
40+
41+
def partialCompare[@sp A](x: Array[A], y: Array[A])(implicit ev: PartialOrder[A]): Double = {
42+
var i = 0
43+
while (i < x.length && i < y.length) {
44+
val cmp = ev.partialCompare(x(i), y(i))
45+
// Double.NaN is also != 0.0
46+
if (cmp != 0.0) return cmp
47+
i += 1
48+
}
49+
signum(x.length - y.length).toDouble
50+
}
51+
}
52+
53+
final private class ArrayEq[@sp A: Eq] extends Eq[Array[A]] with Serializable {
54+
def eqv(x: Array[A], y: Array[A]): Boolean =
55+
ArraySupport.eqv(x, y)
56+
}
57+
58+
final private class ArrayOrder[@sp A: Order] extends Order[Array[A]] with Serializable {
59+
override def eqv(x: Array[A], y: Array[A]): Boolean =
60+
ArraySupport.eqv(x, y)
61+
def compare(x: Array[A], y: Array[A]): Int =
62+
ArraySupport.compare(x, y)
63+
}
64+
65+
final private class ArrayPartialOrder[@sp A: PartialOrder] extends PartialOrder[Array[A]] with Serializable {
66+
override def eqv(x: Array[A], y: Array[A]): Boolean =
67+
ArraySupport.eqv(x, y)
68+
override def partialCompare(x: Array[A], y: Array[A]): Double =
69+
ArraySupport.partialCompare(x, y)
70+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package algebra
2+
package instances
3+
4+
import java.math.MathContext
5+
6+
import algebra.ring._
7+
8+
package object bigDecimal extends BigDecimalInstances
9+
10+
trait BigDecimalInstances extends cats.kernel.instances.BigDecimalInstances {
11+
implicit val bigDecimalAlgebra: BigDecimalAlgebra = new BigDecimalAlgebra()
12+
}
13+
14+
class BigDecimalAlgebra(mc: MathContext) extends Field[BigDecimal] with Serializable {
15+
def this() = this(MathContext.UNLIMITED)
16+
17+
val zero: BigDecimal = BigDecimal(0, mc)
18+
val one: BigDecimal = BigDecimal(1, mc)
19+
20+
def plus(a: BigDecimal, b: BigDecimal): BigDecimal = a + b
21+
def negate(a: BigDecimal): BigDecimal = -a
22+
override def minus(a: BigDecimal, b: BigDecimal): BigDecimal = a - b
23+
24+
def times(a: BigDecimal, b: BigDecimal): BigDecimal = a * b
25+
def div(a: BigDecimal, b: BigDecimal): BigDecimal = a / b
26+
27+
override def pow(a: BigDecimal, k: Int): BigDecimal = a.pow(k)
28+
29+
override def fromInt(n: Int): BigDecimal = BigDecimal(n, mc)
30+
override def fromBigInt(n: BigInt): BigDecimal = BigDecimal(n, mc)
31+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package algebra
2+
package instances
3+
4+
import algebra.ring._
5+
6+
package object bigInt extends BigIntInstances
7+
8+
trait BigIntInstances extends cats.kernel.instances.BigIntInstances {
9+
implicit val bigIntAlgebra: BigIntAlgebra = new BigIntTruncatedDivison
10+
implicit def bigIntTruncatedDivision: TruncatedDivision[BigInt] =
11+
bigIntAlgebra.asInstanceOf[BigIntTruncatedDivison] // Bin-compat hack to avoid allocation
12+
}
13+
14+
class BigIntAlgebra extends EuclideanRing[BigInt] with Serializable {
15+
16+
val zero: BigInt = BigInt(0)
17+
val one: BigInt = BigInt(1)
18+
19+
def plus(a: BigInt, b: BigInt): BigInt = a + b
20+
def negate(a: BigInt): BigInt = -a
21+
override def minus(a: BigInt, b: BigInt): BigInt = a - b
22+
23+
def times(a: BigInt, b: BigInt): BigInt = a * b
24+
25+
override def pow(a: BigInt, k: Int): BigInt = a.pow(k)
26+
27+
override def fromInt(n: Int): BigInt = BigInt(n)
28+
override def fromBigInt(n: BigInt): BigInt = n
29+
30+
override def lcm(a: BigInt, b: BigInt)(implicit ev: Eq[BigInt]): BigInt =
31+
if (a.signum == 0 || b.signum == 0) zero else (a / a.gcd(b)) * b
32+
override def gcd(a: BigInt, b: BigInt)(implicit ev: Eq[BigInt]): BigInt = a.gcd(b)
33+
34+
def euclideanFunction(a: BigInt): BigInt = a.abs
35+
36+
override def equotmod(a: BigInt, b: BigInt): (BigInt, BigInt) = {
37+
val (qt, rt) = a /% b // truncated quotient and remainder
38+
if (rt.signum >= 0) (qt, rt)
39+
else if (b.signum > 0) (qt - 1, rt + b)
40+
else (qt + 1, rt - b)
41+
}
42+
43+
def equot(a: BigInt, b: BigInt): BigInt = {
44+
val (qt, rt) = a /% b // truncated quotient and remainder
45+
if (rt.signum >= 0) qt
46+
else if (b.signum > 0) qt - 1
47+
else qt + 1
48+
}
49+
50+
def emod(a: BigInt, b: BigInt): BigInt = {
51+
val rt = a % b // truncated remainder
52+
if (rt.signum >= 0) rt
53+
else if (b > 0) rt + b
54+
else rt - b
55+
}
56+
57+
}
58+
59+
class BigIntTruncatedDivison extends BigIntAlgebra with TruncatedDivision.forCommutativeRing[BigInt] {
60+
override def tquot(x: BigInt, y: BigInt): BigInt = x / y
61+
override def tmod(x: BigInt, y: BigInt): BigInt = x % y
62+
override def order: Order[BigInt] = cats.kernel.instances.bigInt.catsKernelStdOrderForBigInt
63+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package algebra
2+
package instances
3+
4+
import scala.collection.immutable.BitSet
5+
6+
import algebra.lattice._
7+
8+
package object bitSet extends BitSetInstances
9+
10+
trait BitSetInstances extends cats.kernel.instances.BitSetInstances {
11+
implicit val bitSetAlgebra: BitSetAlgebra =
12+
new BitSetAlgebra
13+
}
14+
15+
class BitSetAlgebra extends GenBool[BitSet] with Serializable {
16+
val zero: BitSet = BitSet.empty
17+
def and(a: BitSet, b: BitSet): BitSet = a & b
18+
def or(a: BitSet, b: BitSet): BitSet = a | b
19+
def without(a: BitSet, b: BitSet): BitSet = a -- b
20+
override def xor(a: BitSet, b: BitSet): BitSet = a ^ b
21+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package algebra
2+
package instances
3+
4+
import algebra.lattice.Bool
5+
import algebra.ring.BoolRing
6+
import algebra.ring.CommutativeRig
7+
8+
package object boolean extends BooleanInstances
9+
10+
trait BooleanInstances extends cats.kernel.instances.BooleanInstances {
11+
implicit val booleanAlgebra: BooleanAlgebra =
12+
new BooleanAlgebra
13+
14+
val booleanRing = new BoolRing[Boolean] {
15+
def zero: Boolean = false
16+
def one: Boolean = true
17+
def plus(x: Boolean, y: Boolean): Boolean = x ^ y
18+
def times(x: Boolean, y: Boolean): Boolean = x && y
19+
}
20+
}
21+
22+
/**
23+
* This commutative rig is different than the one obtained from GF(2).
24+
*
25+
* It uses || for plus, and && for times.
26+
*/
27+
class BooleanAlgebra extends Bool[Boolean] with CommutativeRig[Boolean] {
28+
29+
def zero: Boolean = false
30+
def one: Boolean = true
31+
32+
override def isZero(x: Boolean)(implicit ev: Eq[Boolean]): Boolean = !x
33+
override def isOne(x: Boolean)(implicit ev: Eq[Boolean]): Boolean = x
34+
35+
def and(x: Boolean, y: Boolean): Boolean = x && y
36+
def or(x: Boolean, y: Boolean): Boolean = x || y
37+
def complement(x: Boolean): Boolean = !x
38+
39+
def plus(a: Boolean, b: Boolean): Boolean = a || b
40+
override def pow(a: Boolean, b: Int): Boolean = a
41+
override def times(a: Boolean, b: Boolean): Boolean = a && b
42+
}

0 commit comments

Comments
 (0)