Skip to content

Commit

Permalink
Merge pull request #1231 from ceedubs/nel
Browse files Browse the repository at this point in the history
Finish up work on NonEmptyList
  • Loading branch information
adelbertc authored Jul 27, 2016
2 parents 910a42a + cfdd085 commit 3ab307b
Show file tree
Hide file tree
Showing 17 changed files with 570 additions and 114 deletions.
7 changes: 7 additions & 0 deletions core/src/main/scala/cats/Reducible.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package cats

import cats.data.NonEmptyList

import simulacrum.typeclass

/**
Expand Down Expand Up @@ -113,6 +115,11 @@ import simulacrum.typeclass
def sequence1_[G[_], A](fga: F[G[A]])(implicit G: Apply[G]): G[Unit] =
G.map(reduceLeft(fga)((x, y) => G.map2(x, y)((_, b) => b)))(_ => ())

def toNonEmptyList[A](fa: F[A]): NonEmptyList[A] =
reduceRightTo(fa)(a => NonEmptyList(a, Nil)) { (a, lnel) =>
lnel.map { case NonEmptyList(h, t) => NonEmptyList(a, h :: t) }
}.value

def compose[G[_]: Reducible]: Reducible[λ[α => F[G[α]]]] =
new ComposedReducible[F, G] {
val F = self
Expand Down
4 changes: 2 additions & 2 deletions core/src/main/scala/cats/Traverse.scala
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,9 @@ import simulacrum.typeclass
* scala> import cats.implicits._
* scala> val x: List[ValidatedNel[String, Int]] = List(Validated.valid(1), Validated.invalid("a"), Validated.invalid("b")).map(_.toValidatedNel)
* scala> x.sequenceU
* res0: cats.data.ValidatedNel[String,List[Int]] = Invalid(OneAnd(a,List(b)))
* res0: cats.data.ValidatedNel[String,List[Int]] = Invalid(NonEmptyList(a, b))
* scala> x.sequence[ValidatedNel[String, ?], Int]
* res1: cats.data.ValidatedNel[String,List[Int]] = Invalid(OneAnd(a,List(b)))
* res1: cats.data.ValidatedNel[String,List[Int]] = Invalid(NonEmptyList(a, b))
* }}}
*/
def sequenceU[GA](fga: F[GA])(implicit U: Unapply[Applicative, GA]): U.M[F[U.A]] =
Expand Down
255 changes: 255 additions & 0 deletions core/src/main/scala/cats/data/NonEmptyList.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
package cats
package data

import cats.instances.list._
import cats.syntax.order._

import scala.annotation.tailrec
import scala.collection.mutable.ListBuffer

/**
* A data type which represents a non empty list of A, with
* single element (head) and optional structure (tail).
*/
final case class NonEmptyList[A](head: A, tail: List[A]) {

/**
* Return the head and tail into a single list
*/
def toList: List[A] = head :: tail

/**
* Applies f to all the elements of the structure
*/
def map[B](f: A => B): NonEmptyList[B] =
NonEmptyList(f(head), tail.map(f))

def ++(l: List[A]): NonEmptyList[A] =
NonEmptyList(head, tail ++ l)

def flatMap[B](f: A => NonEmptyList[B]): NonEmptyList[B] =
f(head) ++ tail.flatMap(f andThen (_.toList))

def ::(a: A): NonEmptyList[A] = NonEmptyList(a, head :: tail)

/**
* remove elements not matching the predicate
*/
def filter(p: A => Boolean): List[A] = {
val ftail = tail.filter(p)
if (p(head)) head :: ftail
else ftail
}

/**
* Append another NonEmptyList
*/
def concat(other: NonEmptyList[A]): NonEmptyList[A] =
NonEmptyList(head, tail ::: other.toList)

/**
* Find the first element matching the predicate, if one exists
*/
def find(p: A => Boolean): Option[A] =
if (p(head)) Some(head)
else tail.find(p)

/**
* Check whether at least one element satisfies the predicate
*/
def exists(p: A => Boolean): Boolean =
p(head) || tail.exists(p)

/**
* Check whether all elements satisfy the predicate
*/
def forall(p: A => Boolean): Boolean =
p(head) && tail.forall(p)

/**
* Left-associative fold on the structure using f.
*/
def foldLeft[B](b: B)(f: (B, A) => B): B =
tail.foldLeft(f(b, head))(f)

/**
* Right-associative fold on the structure using f.
*/
def foldRight[B](lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] =
Foldable[List].foldRight(toList, lb)(f)

/**
* Left-associative reduce using f.
*/
def reduceLeft(f: (A, A) => A): A =
tail.foldLeft(head)(f)

def traverse[G[_], B](f: A => G[B])(implicit G: Applicative[G]): G[NonEmptyList[B]] =
G.map2Eval(f(head), Always(Traverse[List].traverse(tail)(f)))(NonEmptyList(_, _)).value

def coflatMap[B](f: NonEmptyList[A] => B): NonEmptyList[B] = {
val buf = ListBuffer.empty[B]
@tailrec def consume(as: List[A]): List[B] =
as match {
case Nil => buf.toList
case a :: as =>
buf += f(NonEmptyList(a, as))
consume(as)
}
NonEmptyList(f(this), consume(tail))
}

def ===(o: NonEmptyList[A])(implicit A: Eq[A]): Boolean =
(this.head === o.head) && this.tail === o.tail

def show(implicit A: Show[A]): String =
toList.iterator.map(A.show).mkString("NonEmptyList(", ", ", ")")

override def toString: String = s"NonEmpty$toList"
}

object NonEmptyList extends NonEmptyListInstances {
def apply[A](head: A, tail: A*): NonEmptyList[A] = NonEmptyList(head, tail.toList)

/**
* Create a `NonEmptyList` from a `List`.
*
* The result will be `None` if the input list is empty and `Some` wrapping a
* `NonEmptyList` otherwise.
*
* @see [[fromListUnsafe]] for an unsafe version that throws an exception if
* the input list is empty.
*/
def fromList[A](l: List[A]): Option[NonEmptyList[A]] =
l match {
case Nil => None
case h :: t => Some(NonEmptyList(h, t))
}

/**
* Create a `NonEmptyList` from a `List`, or throw an
* `IllegalArgumentException` if the input list is empty.
*
* @see [[fromList]] for a safe version that returns `None` if the input list
* is empty.
*/
def fromListUnsafe[A](l: List[A]): NonEmptyList[A] =
l match {
case Nil => throw new IllegalArgumentException("Cannot create NonEmptyList from empty list")
case h :: t => NonEmptyList(h, t)
}

def fromReducible[F[_], A](fa: F[A])(implicit F: Reducible[F]): NonEmptyList[A] =
F.toNonEmptyList(fa)
}

private[data] sealed trait NonEmptyListInstances extends NonEmptyListInstances0 {

implicit val catsDataInstancesForNonEmptyList: SemigroupK[NonEmptyList] with Reducible[NonEmptyList]
with Comonad[NonEmptyList] with Traverse[NonEmptyList] with MonadRec[NonEmptyList] =
new NonEmptyReducible[NonEmptyList, List] with SemigroupK[NonEmptyList]
with Comonad[NonEmptyList] with Traverse[NonEmptyList] with MonadRec[NonEmptyList] {

def combineK[A](a: NonEmptyList[A], b: NonEmptyList[A]): NonEmptyList[A] =
a concat b

override def split[A](fa: NonEmptyList[A]): (A, List[A]) = (fa.head, fa.tail)

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

override def map[A, B](fa: NonEmptyList[A])(f: A => B): NonEmptyList[B] =
fa map f

def pure[A](x: A): NonEmptyList[A] =
NonEmptyList(x, List.empty)

def flatMap[A, B](fa: NonEmptyList[A])(f: A => NonEmptyList[B]): NonEmptyList[B] =
fa flatMap f

def coflatMap[A, B](fa: NonEmptyList[A])(f: NonEmptyList[A] => B): NonEmptyList[B] =
fa coflatMap f

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

def traverse[G[_], A, B](fa: NonEmptyList[A])(f: A => G[B])(implicit G: Applicative[G]): G[NonEmptyList[B]] =
fa traverse f

override def foldLeft[A, B](fa: NonEmptyList[A], b: B)(f: (B, A) => B): B =
fa.foldLeft(b)(f)

override def foldRight[A, B](fa: NonEmptyList[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] =
fa.foldRight(lb)(f)

def tailRecM[A, B](a: A)(f: A => NonEmptyList[A Xor B]): NonEmptyList[B] = {
val buf = new ListBuffer[B]
@tailrec def go(v: NonEmptyList[A Xor B]): Unit = v.head match {
case Xor.Right(b) =>
buf += b
NonEmptyList.fromList(v.tail) match {
case Some(t) => go(t)
case None => ()
}
case Xor.Left(a) => go(f(a) ++ v.tail)
}
go(f(a))
NonEmptyList.fromListUnsafe(buf.result())
}

override def forall[A](fa: NonEmptyList[A])(p: A => Boolean): Boolean =
fa forall p

override def exists[A](fa: NonEmptyList[A])(p: A => Boolean): Boolean =
fa exists p

override def toList[A](fa: NonEmptyList[A]): List[A] = fa.toList

override def toNonEmptyList[A](fa: NonEmptyList[A]): NonEmptyList[A] = fa
}

implicit def catsDataShowForNonEmptyList[A](implicit A: Show[A]): Show[NonEmptyList[A]] =
Show.show[NonEmptyList[A]](_.show)

implicit def catsDataSemigroupForNonEmptyList[A]: Semigroup[NonEmptyList[A]] =
SemigroupK[NonEmptyList].algebra[A]

implicit def catsDataOrderForNonEmptyList[A](implicit A: Order[A]): Order[NonEmptyList[A]] =
new NonEmptyListOrder[A] {
val A0 = A
}
}

private[data] sealed trait NonEmptyListInstances0 extends NonEmptyListInstances1 {
implicit def catsDataPartialOrderForNonEmptyList[A](implicit A: PartialOrder[A]): PartialOrder[NonEmptyList[A]] =
new NonEmptyListPartialOrder[A] {
val A0 = A
}
}

private[data] sealed trait NonEmptyListInstances1 {

implicit def catsDataEqForNonEmptyList[A](implicit A: Eq[A]): Eq[NonEmptyList[A]] =
new NonEmptyListEq[A] {
val A0 = A
}
}

private[data] sealed trait NonEmptyListEq[A] extends Eq[NonEmptyList[A]] {
implicit def A0: Eq[A]

override def eqv(x: NonEmptyList[A], y: NonEmptyList[A]): Boolean = x === y
}

private[data] sealed trait NonEmptyListPartialOrder[A] extends PartialOrder[NonEmptyList[A]] with NonEmptyListEq[A] {
override implicit def A0: PartialOrder[A]

override def partialCompare(x: NonEmptyList[A], y: NonEmptyList[A]): Double =
x.toList partialCompare y.toList
}

private[data] sealed abstract class NonEmptyListOrder[A] extends Order[NonEmptyList[A]] with NonEmptyListPartialOrder[A] {
override implicit def A0: Order[A]

override def compare(x: NonEmptyList[A], y: NonEmptyList[A]): Int =
x.toList compare y.toList
}
25 changes: 13 additions & 12 deletions core/src/main/scala/cats/data/OneAnd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ package cats
package data

import scala.annotation.tailrec
import scala.collection.mutable.ListBuffer
import cats.instances.list._
import scala.collection.mutable.Builder
import cats.instances.stream._

/**
* A data type which represents a single element (head) and some other
Expand Down Expand Up @@ -140,21 +140,22 @@ private[data] sealed trait OneAndInstances extends OneAndLowPriority2 {
}

private[data] trait OneAndLowPriority0 {
implicit val catsDataComonadForOneAnd: Comonad[OneAnd[List, ?]] =
new Comonad[OneAnd[List, ?]] {
def coflatMap[A, B](fa: OneAnd[List, A])(f: OneAnd[List, A] => B): OneAnd[List, B] = {
@tailrec def consume(as: List[A], buf: ListBuffer[B]): List[B] =
as match {
case Nil => buf.toList
case a :: as => consume(as, buf += f(OneAnd(a, as)))
implicit val catsDataComonadForNonEmptyStream: Comonad[OneAnd[Stream, ?]] =
new Comonad[OneAnd[Stream, ?]] {
def coflatMap[A, B](fa: OneAnd[Stream, A])(f: OneAnd[Stream, A] => B): OneAnd[Stream, B] = {
@tailrec def consume(as: Stream[A], buf: Builder[B, Stream[B]]): Stream[B] =
if (as.isEmpty) buf.result
else {
val tail = as.tail
consume(tail, buf += f(OneAnd(as.head, tail)))
}
OneAnd(f(fa), consume(fa.tail, ListBuffer.empty))
OneAnd(f(fa), consume(fa.tail, Stream.newBuilder))
}

def extract[A](fa: OneAnd[List, A]): A =
def extract[A](fa: OneAnd[Stream, A]): A =
fa.head

def map[A, B](fa: OneAnd[List, A])(f: A => B): OneAnd[List, B] =
def map[A, B](fa: OneAnd[Stream, A])(f: A => B): OneAnd[Stream, B] =
fa map f
}
}
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/scala/cats/data/XorT.scala
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ final case class XorT[F[_], A, B](value: F[A Xor B]) {
* scala> val v2: Validated[NonEmptyList[Error], Int] = Validated.Invalid(NonEmptyList("error 2"))
* scala> val xort: XorT[Option, Error, Int] = XorT(Some(Xor.left("error 3")))
* scala> xort.withValidated { v3 => (v1 |@| v2 |@| v3.leftMap(NonEmptyList(_))).map{ case (i, j, k) => i + j + k } }
* res0: XorT[Option, NonEmptyList[Error], Int] = XorT(Some(Left(OneAnd(error 1,List(error 2, error 3)))))
* res0: XorT[Option, NonEmptyList[Error], Int] = XorT(Some(Left(NonEmptyList(error 1, error 2, error 3))))
* }}}
*/
def withValidated[AA, BB](f: Validated[A, B] => Validated[AA, BB])(implicit F: Functor[F]): XorT[F, AA, BB] =
Expand Down
19 changes: 0 additions & 19 deletions core/src/main/scala/cats/data/package.scala
Original file line number Diff line number Diff line change
@@ -1,33 +1,14 @@
package cats

package object data {
type NonEmptyList[A] = OneAnd[List, A]
type NonEmptyStream[A] = OneAnd[Stream, A]
type ValidatedNel[E, A] = Validated[NonEmptyList[E], A]

def NonEmptyList[A](head: A, tail: List[A] = Nil): NonEmptyList[A] =
OneAnd(head, tail)
def NonEmptyList[A](head: A, tail: A*): NonEmptyList[A] =
OneAnd[List, A](head, tail.toList)

def NonEmptyStream[A](head: A, tail: Stream[A] = Stream.empty): NonEmptyStream[A] =
OneAnd(head, tail)
def NonEmptyStream[A](head: A, tail: A*): NonEmptyStream[A] =
OneAnd(head, tail.toStream)

object NonEmptyList {
def fromReducible[F[_], A](fa: F[A])(implicit F: Reducible[F]): Eval[NonEmptyList[A]] =
F.reduceRightTo(fa)(a => NonEmptyList(a, Nil)) { (a, lnel) =>
lnel.map { case OneAnd(h, t) => OneAnd(a, h :: t) }
}

def fromList[A](la: List[A]): Option[NonEmptyList[A]] =
la match {
case (h :: t) => Some(OneAnd(h, t))
case Nil => None
}
}

type ReaderT[F[_], A, B] = Kleisli[F, A, B]
val ReaderT = Kleisli

Expand Down
4 changes: 2 additions & 2 deletions core/src/main/scala/cats/syntax/option.scala
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ final class OptionOps[A](val oa: Option[A]) extends AnyVal {
*
* scala> val error1: Option[String] = Some("error!")
* scala> error1.toInvalidNel(3)
* res0: ValidatedNel[String, Int] = Invalid(OneAnd(error!,List()))
* res0: ValidatedNel[String, Int] = Invalid(NonEmptyList(error!))
*
* scala> val error2: Option[String] = None
* scala> error2.toInvalidNel(3)
Expand Down Expand Up @@ -149,7 +149,7 @@ final class OptionOps[A](val oa: Option[A]) extends AnyVal {
*
* scala> val result2: Option[Int] = None
* scala> result2.toValidNel("error!")
* res1: ValidatedNel[String, Int] = Invalid(OneAnd(error!,List()))
* res1: ValidatedNel[String, Int] = Invalid(NonEmptyList(error!))
* }}}
*/
def toValidNel[B](b: => B): ValidatedNel[B, A] = oa.fold[ValidatedNel[B, A]](Validated.invalidNel(b))(Validated.Valid(_))
Expand Down
Loading

0 comments on commit 3ab307b

Please sign in to comment.