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

Finish up work on NonEmptyList #1231

Merged
merged 13 commits into from
Jul 27, 2016
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
193 changes: 193 additions & 0 deletions core/src/main/scala/cats/data/NonEmptyList.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
package cats
package data

import cats.instances.list._
import cats.syntax.eq._

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))

/**
* remove elements not matching the predicate
*/
def filter(p: A => Boolean): List[A] =
toList.filter(p)
Copy link
Contributor

Choose a reason for hiding this comment

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

this is maybe a little slower than:

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)

toList.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 coflatMap[B](f: NonEmptyList[A] => B): NonEmptyList[B] = {
@tailrec def consume(as: List[A], buf: ListBuffer[B]): List[B] =
Copy link
Contributor

Choose a reason for hiding this comment

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

why pass the mutable buf? Can we capture to make more clear there is only one:

val buf = ListBuffer.empty
@tailrec def consume(as: List[A]): List[B] = {
...
}

as match {
case Nil => buf.toList
case a :: as => consume(as, buf += f(NonEmptyList(a, as)))
}
NonEmptyList(f(this), consume(this.tail, ListBuffer.empty))
Copy link
Contributor

Choose a reason for hiding this comment

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

why this.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)

def fromList[A](l: List[A]): Option[NonEmptyList[A]] =
if (l.isEmpty) None else Some(NonEmptyList(l.head, l.tail))
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 => None
  case h :: t => Some(NonEmptyList(h, t))
}

to make more clear that .head and .tail are safe?


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)
}

if (l.nonEmpty) NonEmptyList(l.head, l.tail)
else throw new IllegalArgumentException("Cannot create NonEmptyList from empty list")
}

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]] =
G.map2Eval(f(fa.head), Always(Traverse[List].traverse(fa.tail)(f)))(NonEmptyList(_, _)).value
Copy link
Contributor

Choose a reason for hiding this comment

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

why not have a .traverse method on NonEmptyList? Why is this different from the others?


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
}

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:Order]: Order[NonEmptyList[A]] =
Order.by(_.toList)
}

private[data] sealed trait NonEmptyListInstances0 extends NonEmptyListInstances1 {
implicit def catsDataPartialOrderForNonEmptyList[A:PartialOrder]: PartialOrder[NonEmptyList[A]] =
PartialOrder.by(_.toList)
}

private[data] sealed trait NonEmptyListInstances1 {

implicit def catsDataEqForNonEmptyList[A](implicit A: Eq[A]): Eq[NonEmptyList[A]] =
new Eq[NonEmptyList[A]] {
def eqv(x: NonEmptyList[A], y: NonEmptyList[A]): Boolean = x === y
}
}
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
11 changes: 10 additions & 1 deletion laws/src/main/scala/cats/laws/discipline/Arbitrary.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,13 @@ object arbitrary extends ArbitraryInstances0 {
// in the context of Int => Boolean. Once scalacheck supports better Function1 arbitrary
// instances this can be removed.
implicit def catsLawsArbitraryForIntToBool: Arbitrary[(Int) => Boolean] =
Arbitrary(getArbitrary[Int].map(x => (input) => input < x))
Arbitrary(
getArbitrary[Int].map(x =>
new Function1[Int, Boolean] {
def apply(i: Int): Boolean = i < x

override def toString = s"<function testing whether input is less than $x>"
}))

implicit def catsLawsArbitraryForConst[A, B](implicit A: Arbitrary[A]): Arbitrary[Const[A, B]] =
Arbitrary(A.arbitrary.map(Const[A, B]))
Expand All @@ -26,6 +32,9 @@ object arbitrary extends ArbitraryInstances0 {
implicit def catsLawsArbitraryForNonEmptyVector[A](implicit A: Arbitrary[A]): Arbitrary[NonEmptyVector[A]] =
Arbitrary(implicitly[Arbitrary[Vector[A]]].arbitrary.flatMap(fa => A.arbitrary.map(a => NonEmptyVector(a, fa))))

implicit def catsLawsArbitraryForNonEmptyList[A](implicit A: Arbitrary[A]): Arbitrary[NonEmptyList[A]] =
Arbitrary(implicitly[Arbitrary[List[A]]].arbitrary.flatMap(fa => A.arbitrary.map(a => NonEmptyList(a, fa))))

implicit def catsLawsArbitraryForXor[A, B](implicit A: Arbitrary[A], B: Arbitrary[B]): Arbitrary[A Xor B] =
Arbitrary(Gen.oneOf(A.arbitrary.map(Xor.left), B.arbitrary.map(Xor.right)))

Expand Down
12 changes: 0 additions & 12 deletions tests/src/test/scala/cats/tests/CokleisliTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,6 @@ class CokleisliTests extends SlowCatsSuite {
// Ceremony to help scalac to do the right thing, see also #267.
type CokleisliNEL[A, B] = Cokleisli[NonEmptyList, A, B]

implicit def ev0[A: Arbitrary, B: Arbitrary]: Arbitrary[CokleisliNEL[A, B]] =
catsLawsArbitraryForCokleisli

implicit def ev1[A: Arbitrary, B: Eq]: Eq[CokleisliNEL[A, B]] =
cokleisliEq[NonEmptyList, A, B](catsLawsArbitraryForOneAnd, Eq[B])

checkAll("Cokleisli[NonEmptyList, Int, Int]", ArrowTests[CokleisliNEL].arrow[Int, Int, Int, Int, Int, Int])
checkAll("Arrow[Cokleisli[NonEmptyList, ?, ?]]", SerializableTests.serializable(Arrow[CokleisliNEL]))
}
Expand All @@ -53,12 +47,6 @@ class CokleisliTests extends SlowCatsSuite {
// More ceremony, see above
type CokleisliNELE[A] = Cokleisli[NonEmptyList, A, A]

implicit def ev0[A: Arbitrary]: Arbitrary[CokleisliNELE[A]] =
catsLawsArbitraryForCokleisli[NonEmptyList, A, A]

implicit def ev1[A: Eq](implicit arb: Arbitrary[A]): Eq[CokleisliNELE[A]] =
cokleisliEqE[NonEmptyList, A](catsLawsArbitraryForOneAnd, Eq[A])

{
implicit val cokleisliMonoidK = Cokleisli.catsDataMonoidKForCokleisli[NonEmptyList]
checkAll("Cokleisli[NonEmptyList, Int, Int]", MonoidKTests[CokleisliNELE].monoidK[Int])
Expand Down
2 changes: 1 addition & 1 deletion tests/src/test/scala/cats/tests/ConstTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import cats.kernel.laws.{GroupLaws, OrderLaws}
import cats.data.{Const, NonEmptyList}
import cats.functor.Contravariant
import cats.laws.discipline._
import cats.laws.discipline.arbitrary.{catsLawsArbitraryForConst, catsLawsArbitraryForOneAnd}
import cats.laws.discipline.arbitrary.{catsLawsArbitraryForConst, catsLawsArbitraryForNonEmptyList}

class ConstTests extends CatsSuite {

Expand Down
4 changes: 2 additions & 2 deletions tests/src/test/scala/cats/tests/FoldableTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@ abstract class FoldableCheck[F[_]: Foldable](name: String)(implicit ArbFInt: Arb
val list = fa.toList
val nelOpt = list.toNel
maxOpt should === (nelOpt.map(_.maximum))
maxOpt should === (nelOpt.map(_.unwrap.max))
maxOpt should === (nelOpt.map(_.toList.max))
minOpt should === (nelOpt.map(_.minimum))
minOpt should === (nelOpt.map(_.unwrap.min))
minOpt should === (nelOpt.map(_.toList.min))
maxOpt.forall(i => fa.forall(_ <= i)) should === (true)
minOpt.forall(i => fa.forall(_ >= i)) should === (true)
}
Expand Down
2 changes: 1 addition & 1 deletion tests/src/test/scala/cats/tests/ListTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class ListTests extends CatsSuite {

test("nel => list => nel returns original nel")(
forAll { fa: NonEmptyList[Int] =>
fa.unwrap.toNel should === (Some(fa))
fa.toList.toNel should === (Some(fa))
}
)

Expand Down
Loading