Skip to content

Finish up work on NonEmptyList #1231

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

Merged
merged 13 commits into from
Jul 27, 2016
Merged
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) }
Copy link
Contributor

Choose a reason for hiding this comment

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

should we add a :: nel to make a new NonEmptyList?

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added.

}.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]) {
Copy link
Contributor

Choose a reason for hiding this comment

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

what if we did something similar to NonEmptyVector and made this a value class around ::[A]? Then we would not need to allocate in some cases.


/**
* 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] =
Copy link
Contributor

Choose a reason for hiding this comment

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

if we did the ::[A] trick, this would require a cast. Maybe the real class is the way to go.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

One thing that we could do would be to define it as a sealed abstract class so we have some flexibility in changing it in the future. However, we still wouldn't be able to change it into a value class without breaking binary compatibility.

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] =
Copy link
Contributor

Choose a reason for hiding this comment

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

what about:

if (p(head)) Some(head)
else tail.find(p)

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] =
Copy link
Contributor

Choose a reason for hiding this comment

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

what about

l match {
  case Nil => throw new IllegalArgumentException("Cannot create NonEmptyList from empty list")
  case h :: t => NonEmptyList(h, t)
}

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