-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
Adding NonEmptyVector #1137
Adding NonEmptyVector #1137
Changes from 10 commits
c6c8ee0
fb7988f
b715ee7
74e8a40
33b845f
d237bdb
a79bc57
a9ceb60
e35ada2
696e269
e173928
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,230 @@ | ||
package cats | ||
package data | ||
|
||
import scala.annotation.tailrec | ||
import scala.collection.immutable.VectorBuilder | ||
import scala.util.Try | ||
import cats.instances.vector._ | ||
|
||
/** | ||
* A data type which represents a non empty Vector. | ||
*/ | ||
final case class NonEmptyVector[A] private (toVector: Vector[A]) { | ||
|
||
/** Gets the element at the index, if it exists */ | ||
def get(i: Int): Option[A] = | ||
Try(getUnsafe(i)).toOption | ||
|
||
/** Gets the element at the index, or throws an exception if none exists */ | ||
def getUnsafe(i: Int): A = toVector(i) | ||
|
||
/** Updates the element at the index, if it exists */ | ||
def updated(i: Int, a: A): Option[NonEmptyVector[A]] = | ||
Try(updatedUnsafe(i, a)).toOption | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. More performance speculation: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oops I guess that would need to wrap it in a |
||
|
||
/** Updates the element at the index, or throws an exeption if none exists */ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Small typo: exeption -> exception. |
||
def updatedUnsafe(i: Int, a: A): | ||
NonEmptyVector[A] = NonEmptyVector(toVector.updated(i, a)) | ||
|
||
def head: A = toVector.head | ||
|
||
def tail: Vector[A] = toVector.tail | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I feel like it's worth supporting There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Or both? :-) But agree on wanting indexed access. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @non you mean I'd be in favor of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes! Sorry, in my defense it was late! ;) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why not have: def get(i: Int): Option[A]
// This is unsafe, it may throw on out-of-bounds, see `get` for a safe variant.
def apply(i: Int): A for updated I would like: def updatedOption(i: Int, a: A): Option[NonEmptyVector[A]]
// this will throw is i is out of bounds.
def updated(i: Int, a: A): NonEmptyVector[A] I feel like adding There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. alternatively, since There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is tough. I think that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can see your point on safety, but there is also a cost (especially to reviewers of future code) of using names that are unsafe on Vector to be safe here (if they change the type signature). My bias is using Maybe I would say: when we can safely follow standard naming conventions, we should. Otherwise, if we want to add methods that can throw, use
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @johnynek everything that you said sounds good to me. So it sounds like it'll be |
||
/** | ||
* remove elements not matching the predicate | ||
*/ | ||
def filter(f: A => Boolean): Vector[A] = toVector.filter(f) | ||
|
||
/** | ||
* Append another NonEmptyVector to this | ||
*/ | ||
def concat(other: NonEmptyVector[A]): NonEmptyVector[A] = NonEmptyVector(toVector ++ other.toVector) | ||
|
||
/** | ||
* Alias for concat | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would be nice to make this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh I see that we are using overloading, so I don't know how that works with scaladoc. I'm generally skeptical of overloading, though it's probably fine in this instance. I think it would prevent us from being able to turn |
||
*/ | ||
def ++(other: NonEmptyVector[A]): NonEmptyVector[A] = concat(other) | ||
|
||
/** | ||
* Append another Vector to this | ||
*/ | ||
def concat(other: Vector[A]): NonEmptyVector[A] = NonEmptyVector(toVector ++ other) | ||
|
||
/** | ||
* Alias for concat | ||
*/ | ||
def ++(other: Vector[A]): NonEmptyVector[A] = concat(other) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again some minor nitpicking about formatting, but both the /**
* Alias for concat
*/ |
||
|
||
/** | ||
* find the first element matching the predicate, if one exists | ||
*/ | ||
def find(f: A => Boolean): Option[A] = toVector.find(f) | ||
|
||
/** | ||
* Check whether at least one element satisfies the predicate. | ||
*/ | ||
def exists(f: A => Boolean): Boolean = toVector.exists(f) | ||
|
||
/** | ||
* Check whether all elements satisfy the predicate. | ||
*/ | ||
def forall(f: A => Boolean): Boolean = toVector.forall(f) | ||
|
||
/** | ||
* Left-associative fold using f. | ||
*/ | ||
def foldLeft[B](b: B)(f: (B, A) => B): B = | ||
toVector.foldLeft(b)(f) | ||
|
||
/** | ||
* Right-associative fold using f. | ||
*/ | ||
def foldRight[B](lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = | ||
Foldable[Vector].foldRight(toVector, lb)(f) | ||
|
||
/** | ||
* Applies f to all the elements | ||
*/ | ||
def map[B](f: A => B): NonEmptyVector[B] = | ||
NonEmptyVector(toVector.map(f)) | ||
|
||
/** | ||
* Applies f to all elements and combines the result | ||
*/ | ||
def flatMap[B](f: A => NonEmptyVector[B]): NonEmptyVector[B] = | ||
NonEmptyVector(toVector.flatMap(a => f(a).toVector)) | ||
|
||
/** | ||
* Left-associative reduce using f. | ||
*/ | ||
def reduceLeft(f: (A, A) => A): A = | ||
tail.foldLeft(head)(f) | ||
|
||
/** | ||
* Left-associative reduce using the Semigroup of A | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should remove this comment: "Left-associative" since |
||
*/ | ||
def reduce(implicit S: Semigroup[A]): A = | ||
S.combineAllOption(tail).foldLeft(head)(S.combine) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. personally I would do: |
||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what about There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think they should be called There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we could add |
||
/** | ||
* Typesafe equality operator. | ||
* | ||
* This method is similar to == except that it only allows two | ||
* NonEmptyVector[A] values to be compared to each other, and uses | ||
* equality provided by Eq[_] instances, rather than using the | ||
* universal equality provided by .equals. | ||
*/ | ||
def ===(that: NonEmptyVector[A])(implicit A: Eq[A]): Boolean = Eq[Vector[A]].eqv(toVector, that.toVector) | ||
|
||
/** | ||
* Typesafe stringification method. | ||
* | ||
* This method is similar to .toString except that it stringifies | ||
* values according to Show[_] instances, rather than using the | ||
* universal .toString method. | ||
*/ | ||
def show(implicit A: Show[A]): String = | ||
s"NonEmptyVector(${Show[Vector[A]].show(toVector)})" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this will give us something like |
||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this show correct? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's better than the |
||
|
||
private[data] sealed trait NonEmptyVectorInstances { | ||
|
||
implicit val catsDataInstancesForNonEmptyVector: SemigroupK[NonEmptyVector] with Reducible[NonEmptyVector] | ||
with Monad[NonEmptyVector] with Comonad[NonEmptyVector] with Traverse[NonEmptyVector] with MonadRec[NonEmptyVector] = | ||
new NonEmptyReducible[NonEmptyVector, Vector] with SemigroupK[NonEmptyVector] with Monad[NonEmptyVector] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this can be a and also I am pretty sure There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we can do 👍 for the |
||
with Comonad[NonEmptyVector] with Traverse[NonEmptyVector] with MonadRec[NonEmptyVector] { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Minor: since |
||
|
||
def combineK[A](a: NonEmptyVector[A], b: NonEmptyVector[A]): NonEmptyVector[A] = | ||
a concat b | ||
|
||
override def split[A](fa: NonEmptyVector[A]): (A, Vector[A]) = (fa.head, fa.tail) | ||
|
||
override def size[A](fa: NonEmptyVector[A]): Long = 1 + fa.tail.size.toLong | ||
|
||
override def reduceLeft[A](fa: NonEmptyVector[A])(f: (A, A) => A): A = | ||
fa.reduceLeft(f) | ||
|
||
override def reduce[A](fa: NonEmptyVector[A])(implicit A: Semigroup[A]): A = | ||
fa.reduce | ||
|
||
override def map[A, B](fa: NonEmptyVector[A])(f: A => B): NonEmptyVector[B] = | ||
fa map f | ||
|
||
def pure[A](x: A): NonEmptyVector[A] = | ||
NonEmptyVector(x, Vector.empty) | ||
|
||
def flatMap[A, B](fa: NonEmptyVector[A])(f: A => NonEmptyVector[B]): NonEmptyVector[B] = | ||
fa flatMap f | ||
|
||
def coflatMap[A, B](fa: NonEmptyVector[A])(f: NonEmptyVector[A] => B): NonEmptyVector[B] = { | ||
@tailrec def consume(as: Vector[A], buf: VectorBuilder[B]): Vector[B] = | ||
as match { | ||
case a +: as => consume(as, buf += f(NonEmptyVector(a, as))) | ||
case _ => buf.result() | ||
} | ||
NonEmptyVector(f(fa), consume(fa.tail, new VectorBuilder[B])) | ||
} | ||
|
||
def extract[A](fa: NonEmptyVector[A]): A = fa.head | ||
|
||
def traverse[G[_], A, B](fa: NonEmptyVector[A])(f: (A) => G[B])(implicit G: Applicative[G]): G[NonEmptyVector[B]] = | ||
G.map2Eval(f(fa.head), Always(Traverse[Vector].traverse(fa.tail)(f)))(NonEmptyVector(_, _)).value | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Slightly nitpicking but the 2 spaces for indentation seem to be missing. |
||
|
||
|
||
override def foldLeft[A, B](fa: NonEmptyVector[A], b: B)(f: (B, A) => B): B = | ||
fa.foldLeft(b)(f) | ||
|
||
override def foldRight[A, B](fa: NonEmptyVector[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = | ||
fa.foldRight(lb)(f) | ||
|
||
def tailRecM[A, B](a: A)(f: A => NonEmptyVector[A Xor B]): NonEmptyVector[B] = { | ||
val buf = new VectorBuilder[B] | ||
@tailrec def go(v: NonEmptyVector[A Xor B]): Unit = v.head match { | ||
case Xor.Right(b) => | ||
buf += b | ||
NonEmptyVector.fromVector(v.tail) match { | ||
case Some(t) => go(t) | ||
case None => () | ||
} | ||
case Xor.Left(a) => go(f(a).concat(v.tail)) | ||
} | ||
go(f(a)) | ||
NonEmptyVector.fromVectorUnsafe(buf.result()) | ||
} | ||
} | ||
|
||
implicit def catsDataEqForNonEmptyVector[A](implicit A: Eq[A]): Eq[NonEmptyVector[A]] = | ||
new Eq[NonEmptyVector[A]]{ | ||
def eqv(x: NonEmptyVector[A], y: NonEmptyVector[A]): Boolean = x === y | ||
} | ||
|
||
implicit def catsDataShowForNonEmptyVector[A](implicit A: Show[A]): Show[NonEmptyVector[A]] = | ||
Show.show[NonEmptyVector[A]](_.show) | ||
|
||
implicit def catsDataSemigroupForNonEmptyVector[A]: Semigroup[NonEmptyVector[A]] = | ||
catsDataInstancesForNonEmptyVector.algebra | ||
|
||
} | ||
|
||
object NonEmptyVector extends NonEmptyVectorInstances { | ||
|
||
def apply[A](head: A, tail: Vector[A]): NonEmptyVector[A] = | ||
NonEmptyVector(head +: tail) | ||
|
||
def apply[A](head: A, tail: A*): NonEmptyVector[A] = { | ||
val buf = Vector.newBuilder[A] | ||
buf += head | ||
tail.foreach(buf += _) | ||
NonEmptyVector(buf.result) | ||
} | ||
|
||
def fromVector[A](vector: Vector[A]): Option[NonEmptyVector[A]] = | ||
vector.headOption.map(apply(_, vector.tail)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. More performance speculation: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1. I think we should do the horrible, but fast stuff internally when we can prove it is correct (and then test it to be sure). |
||
|
||
def fromVectorUnsafe[A](vector: Vector[A]): NonEmptyVector[A] = | ||
fromVector(vector) match { | ||
case Some(v) => v | ||
case None => | ||
throw new IllegalArgumentException("Cannot create NonEmptyVector from empty vector") | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd probably just do an There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1 |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,10 +21,10 @@ type NonEmptyList[A] = OneAnd[List, A] | |
|
||
which is the actual implementation of non-empty lists in cats. By | ||
having the higher kinded type parameter `F[_]`, `OneAnd` is also able | ||
to represent other "non-empty" data structures, e.g. | ||
to represent other "non-empty" data structures e.g. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't feel strongly about this but shall we replace this example with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that after this PR (or possibly as a part of this PR) we can probably rip out |
||
|
||
```tut:silent | ||
import cats.data.OneAnd | ||
|
||
type NonEmptyVector[A] = OneAnd[Vector, A] | ||
``` | ||
type NonEmptyStream[A] = OneAnd[Stream, A] | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that
toVector.lift(i)
would probably be a more efficient implementation (though this is performance speculation without a benchmark).