Skip to content

Commit

Permalink
Add Align typeclass
Browse files Browse the repository at this point in the history
  • Loading branch information
LukaJCB committed Sep 20, 2019
1 parent d0d5f3f commit 2523014
Show file tree
Hide file tree
Showing 36 changed files with 515 additions and 87 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ TAGS
.idea/*
.idea_modules
.DS_Store
.vscode
.sbtrc
*.sublime-project
*.sublime-workspace
Expand Down
3 changes: 3 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,9 @@ def mimaSettings(moduleName: String) =
exclude[MissingClassProblem](
"cats.kernel.compat.scalaVersionMoreSpecific$suppressUnusedImportWarningForScalaVersionMoreSpecific"
)
) ++ //abstract package private classes
Seq(
exclude[DirectMissingMethodProblem]("cats.data.AbstractNonEmptyInstances.this")
)

}
Expand Down
6 changes: 6 additions & 0 deletions core/src/main/scala-2.12/cats/compat/Vector.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package cats.compat

private[cats] object Vector {
def zipWith[A, B, C](fa: Vector[A], fb: Vector[B])(f: (A, B) => C): Vector[C] =
(fa, fb).zipped.map(f)
}
6 changes: 6 additions & 0 deletions core/src/main/scala-2.13+/cats/compat/Vector.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package cats.compat

private[cats] object Vector {
def zipWith[A, B, C](fa: Vector[A], fb: Vector[B])(f: (A, B) => C): Vector[C] =
fa.lazyZip(fb).map(f)
}
50 changes: 26 additions & 24 deletions core/src/main/scala-2.13+/cats/data/NonEmptyLazyList.scala
Original file line number Diff line number Diff line change
Expand Up @@ -330,30 +330,32 @@ class NonEmptyLazyListOps[A](private val value: NonEmptyLazyList[A]) extends Any

sealed abstract private[data] class NonEmptyLazyListInstances extends NonEmptyLazyListInstances1 {

implicit val catsDataInstancesForNonEmptyLazyList
: Bimonad[NonEmptyLazyList] with NonEmptyTraverse[NonEmptyLazyList] with SemigroupK[NonEmptyLazyList] =
new AbstractNonEmptyInstances[LazyList, NonEmptyLazyList] {

def extract[A](fa: NonEmptyLazyList[A]): A = fa.head

def nonEmptyTraverse[G[_]: Apply, A, B](fa: NonEmptyLazyList[A])(f: A => G[B]): G[NonEmptyLazyList[B]] =
Foldable[LazyList]
.reduceRightToOption[A, G[LazyList[B]]](fa.tail)(a => Apply[G].map(f(a))(LazyList.apply(_))) { (a, lglb) =>
Apply[G].map2Eval(f(a), lglb)(_ +: _)
}
.map {
case None => Apply[G].map(f(fa.head))(h => create(LazyList(h)))
case Some(gtail) => Apply[G].map2(f(fa.head), gtail)((h, t) => create(LazyList(h) ++ t))
}
.value

def reduceLeftTo[A, B](fa: NonEmptyLazyList[A])(f: A => B)(g: (B, A) => B): B = fa.reduceLeftTo(f)(g)

def reduceRightTo[A, B](fa: NonEmptyLazyList[A])(f: A => B)(g: (A, cats.Eval[B]) => cats.Eval[B]): cats.Eval[B] =
Eval.defer(fa.reduceRightTo(a => Eval.now(f(a))) { (a, b) =>
Eval.defer(g(a, b))
})
}
implicit val catsDataInstancesForNonEmptyLazyList: Bimonad[NonEmptyLazyList]
with NonEmptyTraverse[NonEmptyLazyList]
with SemigroupK[NonEmptyLazyList]
with Align[NonEmptyLazyList] =
new AbstractNonEmptyInstances[LazyList, NonEmptyLazyList] {

def extract[A](fa: NonEmptyLazyList[A]): A = fa.head

def nonEmptyTraverse[G[_]: Apply, A, B](fa: NonEmptyLazyList[A])(f: A => G[B]): G[NonEmptyLazyList[B]] =
Foldable[LazyList]
.reduceRightToOption[A, G[LazyList[B]]](fa.tail)(a => Apply[G].map(f(a))(LazyList.apply(_))) { (a, lglb) =>
Apply[G].map2Eval(f(a), lglb)(_ +: _)
}
.map {
case None => Apply[G].map(f(fa.head))(h => create(LazyList(h)))
case Some(gtail) => Apply[G].map2(f(fa.head), gtail)((h, t) => create(LazyList(h) ++ t))
}
.value

def reduceLeftTo[A, B](fa: NonEmptyLazyList[A])(f: A => B)(g: (B, A) => B): B = fa.reduceLeftTo(f)(g)

def reduceRightTo[A, B](fa: NonEmptyLazyList[A])(f: A => B)(g: (A, cats.Eval[B]) => cats.Eval[B]): cats.Eval[B] =
Eval.defer(fa.reduceRightTo(a => Eval.now(f(a))) { (a, b) =>
Eval.defer(g(a, b))
})
}

implicit def catsDataOrderForNonEmptyLazyList[A: Order]: Order[NonEmptyLazyList[A]] =
Order[LazyList[A]].asInstanceOf[Order[NonEmptyLazyList[A]]]
Expand Down
27 changes: 25 additions & 2 deletions core/src/main/scala-2.13+/cats/instances/lazyList.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ package cats
package instances
import cats.kernel
import cats.syntax.show._
import cats.data.Ior

import scala.annotation.tailrec

trait LazyListInstances extends cats.kernel.instances.LazyListInstances {
implicit val catsStdInstancesForLazyList
: Traverse[LazyList] with Alternative[LazyList] with Monad[LazyList] with CoflatMap[LazyList] =
new Traverse[LazyList] with Alternative[LazyList] with Monad[LazyList] with CoflatMap[LazyList] {
: Traverse[LazyList] with Alternative[LazyList] with Monad[LazyList] with CoflatMap[LazyList] with Align[LazyList] =
new Traverse[LazyList] with Alternative[LazyList] with Monad[LazyList] with CoflatMap[LazyList]
with Align[LazyList] {

def empty[A]: LazyList[A] = LazyList.empty

Expand Down Expand Up @@ -121,6 +123,27 @@ trait LazyListInstances extends cats.kernel.instances.LazyListInstances {

override def collectFirstSome[A, B](fa: LazyList[A])(f: A => Option[B]): Option[B] =
fa.collectFirst(Function.unlift(f))

def functor: Functor[LazyList] = this

def align[A, B](fa: LazyList[A], fb: LazyList[B]): LazyList[Ior[A, B]] =
alignWith(fa, fb)(identity)

override def alignWith[A, B, C](fa: LazyList[A], fb: LazyList[B])(f: Ior[A, B] => C): LazyList[C] = {
val iterA = fa.iterator
val iterB = fb.iterator

var result: LazyList[C] = LazyList.empty

while (iterA.hasNext || iterB.hasNext) {
val ior =
if (iterA.hasNext && iterB.hasNext) Ior.both(iterA.next(), iterB.next())
else if (iterA.hasNext) Ior.left(iterA.next())
else Ior.right(iterB.next())
result = result :+ f(ior)
}
result
}
}

implicit def catsStdShowForLazyList[A: Show]: Show[LazyList[A]] =
Expand Down
47 changes: 47 additions & 0 deletions core/src/main/scala/cats/Align.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package cats

import simulacrum.typeclass

import cats.data.Ior

/**
* `Align` supports zipping together structures with different shapes,
* holding the results from either or both structures in an `Ior`.
*
* Must obey the laws in cats.laws.AlignLaws
*/
@typeclass trait Align[F[_]] {

def functor: Functor[F]

/**
* Pairs elements of two structures along the union of their shapes, using `Ior` to hold the results.
*
* Align[List].align(List(1, 2), List(10, 11, 12)) = List(Ior.Both(1, 10), Ior.Both(2, 11), Ior.Right(12))
*/
def align[A, B](fa: F[A], fb: F[B]): F[Ior[A, B]]

/**
* Combines elements similarly to `align`, using the provided function to compute the results.
*/
def alignWith[A, B, C](fa: F[A], fb: F[B])(f: Ior[A, B] => C): F[C] =
functor.map(align(fa, fb))(f)

/**
* Align two structures with the same element, combining results according to their semigroup instances.
*/
def alignCombine[A: Semigroup](fa1: F[A], fa2: F[A]): F[A] =
alignWith(fa1, fa2)(_.merge)

/**
* Same as `align`, but forgets from the type that one of the two elements must be present.
*/
def padZip[A, B](fa: F[A], fb: F[B]): F[(Option[A], Option[B])] =
alignWith(fa, fb)(_.pad)

/**
* Same as `alignWith`, but forgets from the type that one of the two elements must be present.
*/
def padZipWith[A, B, C](fa: F[A], fb: F[B])(f: (Option[A], Option[B]) => C): F[C] =
alignWith(fa, fb)(ior => Function.tupled(f)(ior.pad))
}
14 changes: 12 additions & 2 deletions core/src/main/scala/cats/data/AbstractNonEmptyInstances.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,17 @@ package data
abstract private[data] class AbstractNonEmptyInstances[F[_], NonEmptyF[_]](implicit MF: Monad[F],
CF: CoflatMap[F],
TF: Traverse[F],
SF: SemigroupK[F])
SF: SemigroupK[F],
AF: Align[F])
extends Bimonad[NonEmptyF]
with NonEmptyTraverse[NonEmptyF]
with SemigroupK[NonEmptyF] {
with SemigroupK[NonEmptyF]
with Align[NonEmptyF] {
val monadInstance = MF.asInstanceOf[Monad[NonEmptyF]]
val coflatMapInstance = CF.asInstanceOf[CoflatMap[NonEmptyF]]
val traverseInstance = Traverse[F].asInstanceOf[Traverse[NonEmptyF]]
val semiGroupKInstance = SemigroupK[F].asInstanceOf[SemigroupK[NonEmptyF]]
val alignInstance = Align[F].asInstanceOf[Align[NonEmptyF]]

def combineK[A](a: NonEmptyF[A], b: NonEmptyF[A]): NonEmptyF[A] =
semiGroupKInstance.combineK(a, b)
Expand Down Expand Up @@ -78,4 +81,11 @@ abstract private[data] class AbstractNonEmptyInstances[F[_], NonEmptyF[_]](impli
override def collectFirstSome[A, B](fa: NonEmptyF[A])(f: A => Option[B]): Option[B] =
traverseInstance.collectFirstSome(fa)(f)

def align[A, B](fa: NonEmptyF[A], fb: NonEmptyF[B]): NonEmptyF[Ior[A, B]] =
alignInstance.align(fa, fb)

override def functor: Functor[NonEmptyF] = alignInstance.functor

override def alignWith[A, B, C](fa: NonEmptyF[A], fb: NonEmptyF[B])(f: Ior[A, B] => C): NonEmptyF[C] =
alignInstance.alignWith(fa, fb)(f)
}
25 changes: 23 additions & 2 deletions core/src/main/scala/cats/data/Chain.scala
Original file line number Diff line number Diff line change
Expand Up @@ -681,8 +681,8 @@ sealed abstract private[data] class ChainInstances extends ChainInstances1 {
}

implicit val catsDataInstancesForChain
: Traverse[Chain] with Alternative[Chain] with Monad[Chain] with CoflatMap[Chain] =
new Traverse[Chain] with Alternative[Chain] with Monad[Chain] with CoflatMap[Chain] {
: Traverse[Chain] with Alternative[Chain] with Monad[Chain] with CoflatMap[Chain] with Align[Chain] =
new Traverse[Chain] with Alternative[Chain] with Monad[Chain] with CoflatMap[Chain] with Align[Chain] {
def foldLeft[A, B](fa: Chain[A], b: B)(f: (B, A) => B): B =
fa.foldLeft(b)(f)
def foldRight[A, B](fa: Chain[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] =
Expand Down Expand Up @@ -743,6 +743,27 @@ sealed abstract private[data] class ChainInstances extends ChainInstances1 {
}

override def get[A](fa: Chain[A])(idx: Long): Option[A] = fa.get(idx)

def functor: Functor[Chain] = this

def align[A, B](fa: Chain[A], fb: Chain[B]): Chain[Ior[A, B]] =
alignWith(fa, fb)(identity)

override def alignWith[A, B, C](fa: Chain[A], fb: Chain[B])(f: Ior[A, B] => C): Chain[C] = {
val iterA = fa.iterator
val iterB = fb.iterator

var result: Chain[C] = Chain.empty

while (iterA.hasNext || iterB.hasNext) {
val ior =
if (iterA.hasNext && iterB.hasNext) Ior.both(iterA.next(), iterB.next())
else if (iterA.hasNext) Ior.left(iterA.next())
else Ior.right(iterB.next())
result = result :+ f(ior)
}
result
}
}

implicit def catsDataShowForChain[A](implicit A: Show[A]): Show[Chain[A]] =
Expand Down
70 changes: 36 additions & 34 deletions core/src/main/scala/cats/data/NonEmptyChain.scala
Original file line number Diff line number Diff line change
Expand Up @@ -418,40 +418,42 @@ class NonEmptyChainOps[A](private val value: NonEmptyChain[A]) extends AnyVal {

sealed abstract private[data] class NonEmptyChainInstances extends NonEmptyChainInstances1 {

implicit val catsDataInstancesForNonEmptyChain
: SemigroupK[NonEmptyChain] with NonEmptyTraverse[NonEmptyChain] with Bimonad[NonEmptyChain] =
new AbstractNonEmptyInstances[Chain, NonEmptyChain] {
def extract[A](fa: NonEmptyChain[A]): A = fa.head

def nonEmptyTraverse[G[_]: Apply, A, B](fa: NonEmptyChain[A])(f: A => G[B]): G[NonEmptyChain[B]] =
Foldable[Chain]
.reduceRightToOption[A, G[Chain[B]]](fa.tail)(a => Apply[G].map(f(a))(Chain.one)) { (a, lglb) =>
Apply[G].map2Eval(f(a), lglb)(_ +: _)
}
.map {
case None => Apply[G].map(f(fa.head))(NonEmptyChain.one)
case Some(gtail) => Apply[G].map2(f(fa.head), gtail)((h, t) => create(Chain.one(h) ++ t))
}
.value

override def size[A](fa: NonEmptyChain[A]): Long = fa.length

override def reduceLeft[A](fa: NonEmptyChain[A])(f: (A, A) => A): A =
fa.reduceLeft(f)

override def reduce[A](fa: NonEmptyChain[A])(implicit A: Semigroup[A]): A =
fa.reduce

def reduceLeftTo[A, B](fa: NonEmptyChain[A])(f: A => B)(g: (B, A) => B): B = fa.reduceLeftTo(f)(g)

def reduceRightTo[A, B](fa: NonEmptyChain[A])(f: A => B)(g: (A, cats.Eval[B]) => cats.Eval[B]): cats.Eval[B] =
Eval.defer(fa.reduceRightTo(a => Eval.now(f(a))) { (a, b) =>
Eval.defer(g(a, b))
})

override def get[A](fa: NonEmptyChain[A])(idx: Long): Option[A] =
if (idx == 0) Some(fa.head) else fa.tail.get(idx - 1)
}
implicit val catsDataInstancesForNonEmptyChain: SemigroupK[NonEmptyChain]
with NonEmptyTraverse[NonEmptyChain]
with Bimonad[NonEmptyChain]
with Align[NonEmptyChain] =
new AbstractNonEmptyInstances[Chain, NonEmptyChain] {
def extract[A](fa: NonEmptyChain[A]): A = fa.head

def nonEmptyTraverse[G[_]: Apply, A, B](fa: NonEmptyChain[A])(f: A => G[B]): G[NonEmptyChain[B]] =
Foldable[Chain]
.reduceRightToOption[A, G[Chain[B]]](fa.tail)(a => Apply[G].map(f(a))(Chain.one)) { (a, lglb) =>
Apply[G].map2Eval(f(a), lglb)(_ +: _)
}
.map {
case None => Apply[G].map(f(fa.head))(NonEmptyChain.one)
case Some(gtail) => Apply[G].map2(f(fa.head), gtail)((h, t) => create(Chain.one(h) ++ t))
}
.value

override def size[A](fa: NonEmptyChain[A]): Long = fa.length

override def reduceLeft[A](fa: NonEmptyChain[A])(f: (A, A) => A): A =
fa.reduceLeft(f)

override def reduce[A](fa: NonEmptyChain[A])(implicit A: Semigroup[A]): A =
fa.reduce

def reduceLeftTo[A, B](fa: NonEmptyChain[A])(f: A => B)(g: (B, A) => B): B = fa.reduceLeftTo(f)(g)

def reduceRightTo[A, B](fa: NonEmptyChain[A])(f: A => B)(g: (A, cats.Eval[B]) => cats.Eval[B]): cats.Eval[B] =
Eval.defer(fa.reduceRightTo(a => Eval.now(f(a))) { (a, b) =>
Eval.defer(g(a, b))
})

override def get[A](fa: NonEmptyChain[A])(idx: Long): Option[A] =
if (idx == 0) Some(fa.head) else fa.tail.get(idx - 1)
}

implicit def catsDataOrderForNonEmptyChain[A: Order]: Order[NonEmptyChain[A]] =
Order[Chain[A]].asInstanceOf[Order[NonEmptyChain[A]]]
Expand Down
23 changes: 21 additions & 2 deletions core/src/main/scala/cats/data/NonEmptyList.scala
Original file line number Diff line number Diff line change
Expand Up @@ -510,9 +510,9 @@ object NonEmptyList extends NonEmptyListInstances {
sealed abstract private[data] class NonEmptyListInstances extends NonEmptyListInstances0 {

implicit val catsDataInstancesForNonEmptyList
: SemigroupK[NonEmptyList] with Bimonad[NonEmptyList] with NonEmptyTraverse[NonEmptyList] =
: SemigroupK[NonEmptyList] with Bimonad[NonEmptyList] with NonEmptyTraverse[NonEmptyList] with Align[NonEmptyList] =
new NonEmptyReducible[NonEmptyList, List] with SemigroupK[NonEmptyList] with Bimonad[NonEmptyList]
with NonEmptyTraverse[NonEmptyList] {
with NonEmptyTraverse[NonEmptyList] with Align[NonEmptyList] {

def combineK[A](a: NonEmptyList[A], b: NonEmptyList[A]): NonEmptyList[A] =
a.concatNel(b)
Expand Down Expand Up @@ -617,6 +617,25 @@ sealed abstract private[data] class NonEmptyListInstances extends NonEmptyListIn

override def get[A](fa: NonEmptyList[A])(idx: Long): Option[A] =
if (idx == 0) Some(fa.head) else Foldable[List].get(fa.tail)(idx - 1)

def functor: Functor[NonEmptyList] = this

def align[A, B](fa: NonEmptyList[A], fb: NonEmptyList[B]): NonEmptyList[Ior[A, B]] =
alignWith(fa, fb)(identity)

override def alignWith[A, B, C](fa: NonEmptyList[A], fb: NonEmptyList[B])(f: Ior[A, B] => C): NonEmptyList[C] = {

@tailrec
def go(as: List[A], bs: List[B], acc: List[C]): List[C] = (as, bs) match {
case (Nil, Nil) => acc
case (Nil, y :: ys) => go(Nil, ys, f(Ior.right(y)) :: acc)
case (x :: xs, Nil) => go(xs, Nil, f(Ior.left(x)) :: acc)
case (x :: xs, y :: ys) => go(xs, ys, f(Ior.both(x, y)) :: acc)
}

NonEmptyList(f(Ior.both(fa.head, fb.head)), go(fa.tail, fb.tail, Nil).reverse)
}

}

implicit def catsDataShowForNonEmptyList[A](implicit A: Show[A]): Show[NonEmptyList[A]] =
Expand Down
9 changes: 7 additions & 2 deletions core/src/main/scala/cats/data/NonEmptyMapImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -268,8 +268,8 @@ sealed class NonEmptyMapOps[K, A](val value: NonEmptyMap[K, A]) {
sealed abstract private[data] class NonEmptyMapInstances extends NonEmptyMapInstances0 {

implicit def catsDataInstancesForNonEmptyMap[K: Order]
: SemigroupK[NonEmptyMap[K, *]] with NonEmptyTraverse[NonEmptyMap[K, *]] =
new SemigroupK[NonEmptyMap[K, *]] with NonEmptyTraverse[NonEmptyMap[K, *]] {
: SemigroupK[NonEmptyMap[K, *]] with NonEmptyTraverse[NonEmptyMap[K, *]] with Align[NonEmptyMap[K, *]] =
new SemigroupK[NonEmptyMap[K, *]] with NonEmptyTraverse[NonEmptyMap[K, *]] with Align[NonEmptyMap[K, *]] {

override def map[A, B](fa: NonEmptyMap[K, A])(f: A => B): NonEmptyMap[K, B] =
fa.map(f)
Expand Down Expand Up @@ -316,6 +316,11 @@ sealed abstract private[data] class NonEmptyMapInstances extends NonEmptyMapInst

override def toNonEmptyList[A](fa: NonEmptyMap[K, A]): NonEmptyList[A] =
NonEmptyList(fa.head._2, fa.tail.toList.map(_._2))

def functor: Functor[NonEmptyMap[K, *]] = this

def align[A, B](fa: NonEmptyMap[K, A], fb: NonEmptyMap[K, B]): NonEmptyMap[K, Ior[A, B]] =
NonEmptyMap.fromMapUnsafe(Align[SortedMap[K, *]].align(fa.toSortedMap, fb.toSortedMap))
}

implicit def catsDataHashForNonEmptyMap[K: Hash: Order, A: Hash]: Hash[NonEmptyMap[K, A]] =
Expand Down
Loading

0 comments on commit 2523014

Please sign in to comment.