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

SemigroupK/MonoidK methods #4084

Merged
merged 33 commits into from
Mar 12, 2022
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
0e0f0d4
Add methods from seimgroup/monoid to semigroupk/monoidk
TimWSpence Dec 15, 2021
9ea68b4
Update scaladoc
TimWSpence Dec 15, 2021
9f348f7
More scaladoc
TimWSpence Dec 15, 2021
b0b1404
Remove duplicate tests
TimWSpence Dec 15, 2021
72fe714
Fix 2.12 build
TimWSpence Dec 15, 2021
9fb1bd1
Fix docs
TimWSpence Dec 15, 2021
e6fa448
Actually fix docs :facepalm:
TimWSpence Dec 15, 2021
c4a620e
Port semigroup/monoid laws to semigroupk/monoidk
TimWSpence Jan 12, 2022
0d7de79
Cleanup comments
TimWSpence Jan 12, 2022
9264e7c
Optimize combineAllK for Option
TimWSpence Jan 12, 2022
5242b6c
DRY
TimWSpence Jan 12, 2022
93de0b8
Override combineAllK for list
TimWSpence Jan 12, 2022
c2d3d4f
Override combineAllK for Seq
TimWSpence Jan 12, 2022
7099f59
Override combineAllK for vector
TimWSpence Jan 12, 2022
9c07dde
Override combineAllK for Queue
TimWSpence Jan 12, 2022
47232bf
(Hopefully) fix version-specific errors for IterableOnce
TimWSpence Jan 12, 2022
4322e8b
Fix error message
TimWSpence Jan 13, 2022
05763b2
Only require optimizing combineAllOptionK and not combineAllOption as…
TimWSpence Jan 13, 2022
c237e17
Avoid creating closure in comineAllOptionK for Option
TimWSpence Jan 13, 2022
2e5367a
Optimize combineAllOptionK for sequence types
TimWSpence Jan 13, 2022
bcc07d8
Eliminate unnecessary iterator allocation
TimWSpence Jan 13, 2022
e350e70
Add Alternative#fromIterableOnce and Alternative#fromFoldable
TimWSpence Jan 13, 2022
fee0625
Argh scalafmt
TimWSpence Jan 13, 2022
6b5f7fa
Law for Alterantive#fromIterableOnce plus optimized implementations
TimWSpence Jan 28, 2022
b531693
Argh scala 2.12 - why do you do this to me? :(
TimWSpence Jan 28, 2022
f5bdeca
Use method not deprecated in 2.12
TimWSpence Jan 28, 2022
7a0cd81
More efficient LazyList combineAllOptionK which preserves laziness
TimWSpence Jan 28, 2022
361b6c8
Make Alternative#fromFoldable final
TimWSpence Jan 28, 2022
12053e1
Remove unnecessary Arbitrary instance and fix 2.12 issues
TimWSpence Jan 28, 2022
a02f41e
Remove unncessary and binary incompatible added implicit param
TimWSpence Jan 28, 2022
19b3338
I really don't think there is a cyclic dependency dotty...
TimWSpence Feb 24, 2022
549a9f0
Avoid unnecessary allocation
TimWSpence Mar 3, 2022
a744378
Optimize fromIterableOnce for Chain
TimWSpence Mar 12, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions core/src/main/scala-2.12/cats/instances/stream.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ package cats
package instances

import cats.data.{Ior, ZipStream}
import cats.kernel.compat.scalaVersionSpecific._
import cats.syntax.show._

import scala.annotation.tailrec

@suppressUnusedImportWarningForScalaVersionSpecific
trait StreamInstances extends cats.kernel.instances.StreamInstances {

implicit val catsStdInstancesForStream
Expand All @@ -16,6 +18,8 @@ trait StreamInstances extends cats.kernel.instances.StreamInstances {

def combineK[A](x: Stream[A], y: Stream[A]): Stream[A] = x #::: y

override def fromIterableOnce[A](as: IterableOnce[A]): Stream[A] = as.iterator.toStream

override def prependK[A](a: A, fa: Stream[A]): Stream[A] = a #:: fa

def pure[A](x: A): Stream[A] = Stream(x)
Expand Down
2 changes: 2 additions & 0 deletions core/src/main/scala-2.13+/cats/instances/arraySeq.scala
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ private[cats] object ArraySeqInstances {
def combineK[A](xs: ArraySeq[A], ys: ArraySeq[A]): ArraySeq[A] =
xs.concat(ys)

override def fromIterableOnce[A](as: IterableOnce[A]): ArraySeq[A] = ArraySeq.untagged.from(as)

override def prependK[A](a: A, fa: ArraySeq[A]): ArraySeq[A] = fa.prepended(a)

override def appendK[A](fa: ArraySeq[A], a: A): ArraySeq[A] = fa.appended(a)
Expand Down
9 changes: 9 additions & 0 deletions core/src/main/scala-2.13+/cats/instances/lazyList.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ package cats
package instances

import cats.kernel
import cats.kernel.compat.scalaVersionSpecific._
import cats.syntax.show._
import cats.data.Ior
import cats.data.ZipLazyList

import scala.annotation.tailrec

@suppressUnusedImportWarningForScalaVersionSpecific
trait LazyListInstances extends cats.kernel.instances.LazyListInstances {

implicit val catsStdInstancesForLazyList
Expand All @@ -22,6 +24,13 @@ trait LazyListInstances extends cats.kernel.instances.LazyListInstances {

def combineK[A](x: LazyList[A], y: LazyList[A]): LazyList[A] = x.lazyAppendedAll(y)

override def combineAllOptionK[A](as: IterableOnce[LazyList[A]]): Option[LazyList[A]] = {
val iter = as.iterator
if (iter.isEmpty) None else Some(LazyList.from(iter.flatMap(_.iterator)))
}

override def fromIterableOnce[A](as: IterableOnce[A]): LazyList[A] = LazyList.from(as)

override def prependK[A](a: A, fa: LazyList[A]): LazyList[A] = fa.prepended(a)

override def appendK[A](fa: LazyList[A], a: A): LazyList[A] = fa.appended(a)
Expand Down
4 changes: 4 additions & 0 deletions core/src/main/scala-2.13+/cats/instances/stream.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ package cats
package instances

import cats.data.{Ior, ZipStream}
import cats.kernel.compat.scalaVersionSpecific._
import cats.syntax.show._

import scala.annotation.tailrec

@suppressUnusedImportWarningForScalaVersionSpecific
trait StreamInstances extends cats.kernel.instances.StreamInstances {

@deprecated("Use cats.instances.lazyList", "2.0.0-RC2")
Expand All @@ -17,6 +19,8 @@ trait StreamInstances extends cats.kernel.instances.StreamInstances {

def combineK[A](x: Stream[A], y: Stream[A]): Stream[A] = x #::: y

override def fromIterableOnce[A](as: IterableOnce[A]): Stream[A] = Stream.from(as)

override def prependK[A](a: A, fa: Stream[A]): Stream[A] = a #:: fa

def pure[A](x: A): Stream[A] = Stream(x)
Expand Down
8 changes: 8 additions & 0 deletions core/src/main/scala/cats/Alternative.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cats

import simulacrum.typeclass
import cats.kernel.compat.scalaVersionSpecific._

@typeclass trait Alternative[F[_]] extends NonEmptyAlternative[F] with MonoidK[F] { self =>

Expand Down Expand Up @@ -99,8 +100,15 @@ import simulacrum.typeclass
val F = self
val G = Applicative[G]
}

def fromIterableOnce[A](as: IterableOnce[A]): F[A] =
combineAllK(as.iterator.map(pure(_)))

final def fromFoldable[G[_]: Foldable, A](as: G[A]): F[A] =
Copy link
Contributor

Choose a reason for hiding this comment

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

I may have suggested making this final, but on second thought I think it should not be.

It could be that F is very efficient to build from a foldRight or a foldLeft or a foldMap. Since we should assume that the foldable for G knows the most efficient way to do those, the F implementation here may want to call one of those rather than converting to an Iterable[A] and going from there.

This is probably minor, and maybe never a big deal, but the final will block this (I may have suggested or previously agreed with final, if so, apologies for the flip-flop).

Copy link
Member

Choose a reason for hiding this comment

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

Oh yeah, that's a good point.

FTR this was my suggestion in #4084 (comment).

The problem is we can't currently check this implementation in the laws; see #4084 (comment).

So there's a bit of performance vs yolo factor here 😉

This is probably minor, and maybe never a big deal, but the final will block this

We can always take away a final modifier, but we can't add it back. So I'd be fine to leave this until a practical need to change this comes up.

Copy link
Contributor

Choose a reason for hiding this comment

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

why can't we check the laws? why can't we check that fromFoldable(g) == fromIterable(g.toIterable) in the laws?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah AFAICT we would have to add extra type parameters to AlternativeTests#alternative to do this. Unless I've missed something?

fromIterableOnce(Foldable[G].toIterable(as))
}

@suppressUnusedImportWarningForScalaVersionSpecific
object Alternative {

/* ======================================================================== */
Expand Down
63 changes: 63 additions & 0 deletions core/src/main/scala/cats/MonoidK.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package cats

import cats.kernel.compat.scalaVersionSpecific._

import simulacrum.typeclass

/**
Expand Down Expand Up @@ -36,6 +38,21 @@ import simulacrum.typeclass
*/
def empty[A]: F[A]

/**
* Tests if `a` is the identity.
*
* Example:
* {{{
* scala> MonoidK[List].isEmpty(List.empty[String])
* res0: Boolean = true
*
* scala> MonoidK[List].isEmpty(List("something"))
* res1: Boolean = false
* }}}
*/
def isEmpty[A](a: F[A])(implicit ev: Eq[F[A]]): Boolean =
ev.eqv(a, empty)

/**
* Given a type A, create a concrete Monoid[F[A]].
*
Expand Down Expand Up @@ -67,8 +84,54 @@ import simulacrum.typeclass
new ComposedMonoidK[F, G] {
val F: MonoidK[F] = self
}

/**
* Return `a` combined with itself `n` times.
*
* Example:
* {{{
* scala> SemigroupK[List].combineNK(List(1), 5)
* res0: List[Int] = List(1, 1, 1, 1, 1)

* scala> MonoidK[List].combineNK(List("ha"), 0)
* res1: List[String] = List()
*
* }}}
*/
override def combineNK[A](a: F[A], n: Int): F[A] =
if (n < 0) throw new IllegalArgumentException("Repeated combining for monoidKs must have n >= 0")
else if (n == 0) empty[A]
else repeatedCombineNK(a, n)

/**
* Given a sequence of `as`, sum them using the monoidK and return the total.
*
* Example:
* {{{
* scala> MonoidK[List].combineAllK(List(List("One"), List("Two"), List("Three")))
* res0: List[String] = List(One, Two, Three)
*
* scala> MonoidK[List].combineAllK[String](List.empty)
* res1: List[String] = List()
* }}}
*/
def combineAllK[A](as: IterableOnce[F[A]]): F[A] =
combineAllOptionK(as) match {
case Some(fa) => fa
case None => empty[A]
}

override def reverse: MonoidK[F] =
new MonoidK[F] {
def empty[A] = self.empty
def combineK[A](a: F[A], b: F[A]) = self.combineK(b, a)
// a + a + a + ... is the same when reversed
override def combineNK[A](a: F[A], n: Int): F[A] = self.combineNK(a, n)
override def reverse = self
}
}

@suppressUnusedImportWarningForScalaVersionSpecific
object MonoidK {

/* ======================================================================== */
Expand Down
60 changes: 60 additions & 0 deletions core/src/main/scala/cats/SemigroupK.scala
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package cats

import scala.annotation.tailrec
import scala.collection.immutable.{Seq, SortedMap, SortedSet}
import simulacrum.typeclass
import cats.data.Ior
import cats.kernel.compat.scalaVersionSpecific._

/**
* SemigroupK is a universal semigroup which operates on kinds.
Expand Down Expand Up @@ -108,8 +110,66 @@ import cats.data.Ior
*/
def sum[A, B](fa: F[A], fb: F[B])(implicit F: Functor[F]): F[Either[A, B]] =
combineK(F.map(fa)(Left(_)), F.map(fb)(Right(_)))

/**
* Return `a` combined with itself `n` times.
*
* Example:
* {{{
* scala> SemigroupK[List].combineNK(List(1), 5)
* res0: List[Int] = List(1, 1, 1, 1, 1)
*
* }}}
*/
def combineNK[A](a: F[A], n: Int): F[A] =
if (n <= 0) throw new IllegalArgumentException("Repeated combining for semigroupKs must have n > 0")
else repeatedCombineNK(a, n)

/**
* Return `a` combined with itself more than once.
*/
protected[this] def repeatedCombineNK[A](a: F[A], n: Int): F[A] = {
@tailrec def loop(b: F[A], k: Int, extra: F[A]): F[A] =
if (k == 1) combineK(b, extra)
else {
val x = if ((k & 1) == 1) combineK(b, extra) else extra
loop(combineK(b, b), k >>> 1, x)
}
if (n == 1) a else loop(a, n - 1, a)
}

/**
* Given a sequence of `as`, combine them and return the total.
*
* If the sequence is empty, returns None. Otherwise, returns Some(total).
*
* Example:
* {{{
* scala> SemigroupK[List].combineAllOptionK(List(List("One"), List("Two"), List("Three")))
* res0: Option[List[String]] = Some(List(One, Two, Three))
*
* scala> SemigroupK[List].combineAllOptionK[String](List.empty)
* res1: Option[List[String]] = None
* }}}
*/
def combineAllOptionK[A](as: IterableOnce[F[A]]): Option[F[A]] =
as.iterator.reduceOption(combineK[A])

/**
* return a semigroupK that reverses the order
* so combineK(a, b) == reverse.combineK(b, a)
*/
def reverse: SemigroupK[F] =
new SemigroupK[F] {
def combineK[A](a: F[A], b: F[A]): F[A] = self.combineK(b, a)
// a + a + a + ... is the same when reversed
override def combineNK[A](a: F[A], n: Int): F[A] = self.combineNK(a, n)
override def reverse = self
}

}

@suppressUnusedImportWarningForScalaVersionSpecific
object SemigroupK extends ScalaVersionSpecificMonoidKInstances with SemigroupKInstances0 {
def align[F[_]: SemigroupK: Functor]: Align[F] =
new Align[F] {
Expand Down
3 changes: 3 additions & 0 deletions core/src/main/scala/cats/data/Chain.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package data

import Chain._
import cats.kernel.instances.StaticMethods
import cats.kernel.compat.scalaVersionSpecific._

import scala.annotation.tailrec
import scala.collection.immutable.{IndexedSeq => ImIndexedSeq, SortedMap, TreeSet}
Expand Down Expand Up @@ -678,6 +679,7 @@ sealed abstract class Chain[+A] {
}
}

@suppressUnusedImportWarningForScalaVersionSpecific
object Chain extends ChainInstances {

private val sentinel: Function1[Any, Any] = new scala.runtime.AbstractFunction1[Any, Any] { def apply(a: Any) = this }
Expand Down Expand Up @@ -1029,6 +1031,7 @@ sealed abstract private[data] class ChainInstances extends ChainInstances1 {

def empty[A]: Chain[A] = Chain.nil
def combineK[A](c: Chain[A], c2: Chain[A]): Chain[A] = Chain.concat(c, c2)
override def fromIterableOnce[A](as: IterableOnce[A]): Chain[A] = Chain.fromSeq(as.iterator.toSeq)
def pure[A](a: A): Chain[A] = Chain.one(a)
def flatMap[A, B](fa: Chain[A])(f: A => Chain[B]): Chain[B] =
fa.flatMap(f)
Expand Down
10 changes: 10 additions & 0 deletions core/src/main/scala/cats/instances/list.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package cats
package instances

import cats.data.{Chain, ZipList}
import cats.instances.instances.appendAll
import cats.kernel.compat.scalaVersionSpecific._
import cats.kernel.instances.StaticMethods.wrapMutableIndexedSeq
import cats.syntax.show._

Expand All @@ -19,6 +21,13 @@ trait ListInstances extends cats.kernel.instances.ListInstances {

def combineK[A](x: List[A], y: List[A]): List[A] = x ::: y

override def combineAllOptionK[A](as: IterableOnce[List[A]]): Option[List[A]] = {
val iter = as.iterator
if (iter.isEmpty) None else Some(appendAll(iter, List.newBuilder[A]).result())
}

override def fromIterableOnce[A](as: IterableOnce[A]): List[A] = as.iterator.toList

override def prependK[A](a: A, fa: List[A]): List[A] = a :: fa

override def appendK[A](fa: List[A], a: A): List[A] = fa :+ a
Expand Down Expand Up @@ -262,6 +271,7 @@ trait ListInstances extends cats.kernel.instances.ListInstances {
}
}

@suppressUnusedImportWarningForScalaVersionSpecific
private[instances] trait ListInstancesBinCompat0 {
implicit val catsStdTraverseFilterForList: TraverseFilter[List] = new TraverseFilter[List] {
val traverse: Traverse[List] = cats.instances.list.catsStdInstancesForList
Expand Down
21 changes: 21 additions & 0 deletions core/src/main/scala/cats/instances/option.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package instances

import scala.annotation.tailrec
import cats.data.Ior
import cats.kernel.compat.scalaVersionSpecific._

trait OptionInstances extends cats.kernel.instances.OptionInstances {

Expand All @@ -25,6 +26,25 @@ trait OptionInstances extends cats.kernel.instances.OptionInstances {

def combineK[A](x: Option[A], y: Option[A]): Option[A] = if (x.isDefined) x else y

override def combineAllOptionK[A](as: IterableOnce[Option[A]]): Option[Option[A]] = {
val it = as.iterator
if (it.hasNext) {
while (true) {
val o = it.next()
if (o.isDefined) return Some(o)
if (!it.hasNext) return Some(None)
}
sys.error("unreachable")
} else {
return None
}
}

override def fromIterableOnce[A](as: IterableOnce[A]): Option[A] = {
val iter = as.iterator
if (iter.hasNext) Some(iter.next()) else None
}

override def prependK[A](a: A, fa: Option[A]): Option[A] = Some(a)
override def appendK[A](fa: Option[A], a: A): Option[A] = if (fa.isDefined) fa else Some(a)

Expand Down Expand Up @@ -212,6 +232,7 @@ trait OptionInstances extends cats.kernel.instances.OptionInstances {
}
}

@suppressUnusedImportWarningForScalaVersionSpecific
private[instances] trait OptionInstancesBinCompat0 {
implicit val catsStdTraverseFilterForOption: TraverseFilter[Option] = new TraverseFilter[Option] {
val traverse: Traverse[Option] = cats.instances.option.catsStdInstancesForOption
Expand Down
14 changes: 14 additions & 0 deletions core/src/main/scala/cats/instances/package.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package cats.instances

import scala.collection.mutable.Builder

private[instances] object instances {

def appendAll[F <: Iterable[A], A](it: Iterator[F], bldr: Builder[A, F]): bldr.type = {
while (it.hasNext) {
bldr ++= it.next()
}
bldr
}

}
Loading