Missing NonEmpty Collection Helper methods
In the Cats library there are several aliases for various NonEmpty types and methods, for example EitherNec and 1.leftNec.
However, there are a lot of inconsistencies with when a *Nec or *Nel or other non-empty collection version of a method exists.
We have the following non-empty collection types.
NonEmptyChainNonEmptyLazyListNonEmptyListNonEmptyMap(Doesn't seem to be used for any 'left' sides so not included below)NonEmptySeqNonEmptySetNonEmptyVector
See also PR: 3998 For a WIP to add a few more methods not listed here.
Below is an accounting of the missing methods. I would be happy to do a PR to add the below methods, but before I did, I wanted to make sure there isn't some underlying principle, or typeclass, or something we could use to optimize this to a degree so less of these helper methods will be added, while still granting their utility.
Some Ideas besides just adding the methods:
- Adding
toNe*methods to theBifunctorsyntax/ops. - Add a
OneOftypeclass (sort of thing) of the formtrait OneOf[F[_], A]{ def one(a: A): F[A]}and make a add a genericleftLiftmethod onBifunctor.-
OneOfdiffers fromPurein AlleyCats, because it allows you to say:implicit def oneOfNes[A: Order]: OneOf[NonEmptySet, A] = a => NonEmptySet[F, A].one(a)implicit def oneOfApplicative[F[_]: Applicative, A]: OneOf[F, A] = a => Applicative[F].pure(a)
-
either.leftLift[NonEmptyLazyList]: EitherNell[A, B] -
Added bonus, this works with Non-NonEmpty lists as well!
either.leftLift[List]: Either[List[A], B]
-
Either
Type Aliases
// Exists:
type EitherNel[+E, +A] = Either[NonEmptyList[E], A]
type EitherNec[+E, +A] = Either[NonEmptyChain[E], A]
type EitherNes[E, +A] = Either[NonEmptySet[E], A]
// Missing:
type EitherNeSeq[+E, +A] = Either[NonEmptySeq[E], A] // NeSeq isn't a great name, happy to hear ideas for other aliases.
type EitherNev[+E, +A] = Either[NonEmptyVector[E], A]
type EitherNell[+E, +A] = Either[NonEmptyLazyList[E], A] // Scala 2.13+ Only
Object Ops
final class EitherObjectOps(private val either: Either.type) extends AnyVal {
// Exists:
def leftNec[A, B](a: A): EitherNec[A, B]
def leftNel[A, B](a: A): EitherNel[A, B]
def leftNes[A, B](a: A)(implicit O: Order[A]): EitherNes[A, B]
def rightNec[A, B](b: B): EitherNec[A, B]
def rightNel[A, B](b: B): EitherNel[A, B]
def rightNes[A, B](b: B)(implicit O: Order[B]): EitherNes[A, B]
// Missing:
def leftNell[A, B](a: A): EitherNell[A, B] // Scala 2.13+ Only
def leftNeSeq[A, B](a: A): EitherNeSeq[A, B]
def leftNeV[A, B](a: A): EitherNeV[A, B]
def rightNell[A, B](b: B): EitherNell[A, B] // Scala 2.13+ Only
def rightNeSeq[A, B](b: B): EitherNeSeq[A, B]
def rightNeV[A, B](b: B): EitherNeV[A, B]
}
Ops
final class EitherOps[A, B](private val eab: Either[A, B]) extends AnyVal {
// Exists:
def toValidatedNel[AA >: A]: ValidatedNel[AA, B]
def toEitherNel[AA >: A]: EitherNel[AA, B]
def toEitherNec[AA >: A]: EitherNec[AA, B]
def toEitherNes[AA >: A](implicit O: Order[AA]): EitherNes[AA, B]
// Missing:
def toValidatedNec[AA >: A]: ValidatedNec[AA, B]
def toValidatedNell[AA >: A]: ValidatedNell[AA, B] // Scala 2.13+ Only
def toValidatedNes[AA >: A](implicit O: Order[AA]): ValidatedNes[AA, B]
def toValidatedNeSeq[AA >: A]: ValidatedNeSeq[AA, B]
def toValidatedNev[AA >: A]: ValidatedNev[AA, B]
def toEitherNell[AA >: A]: EitherNell[AA, B] // Scala 2.13+ Only
def toEitherNeSeq[AA >: A]: EitherNeSeq[AA, B]
def toEitherNev[AA >: A]: EitherNev[AA, B]
}
Id Ops
class EitherIdOps[A](private val value: A) extends AnyVal {
// Exist:
def toValidatedNec: ValidatedNec[A, B]
def leftNec[B]: EitherNec[A, B]
def leftNel[B]: EitherNel[A, B]
def rightNec[B]: EitherNec[B, A]
def rightNel[B]: EitherNel[B, A]
// Missing:
def toValidatedNec: ValidatedNec[A, B]
def toValidatedNell: ValidatedNell[A, B] // Scala 2.13+ Only
def toValidatedNes(implicit O: Order[A]): ValidatedNes[A, B]
def toValidatedNeSeq: ValidatedNeSeq[A, B]
def toValidatedNev: ValidatedNev[A, B]
def leftNell[B]: EitherNell[A, B] // Scala 2.13+ Only
def leftNes[B](implicit O: Order[A]): EitherNes[A, B]
def leftNeSeq[B]: EitherNeSeq[A, B]
def leftNev[B]: EitherNev[A, B]
def rightNell[B]: EitherNell[B, A] // Scala 2.13+ Only
def rightNes[B](implicit O: Order[A]): EitherNes[B, A]
def rightNeSeq[B]: EitherNeSeq[B, A]
def rightNev[B]: EitherNev[B, A]
}
EitherT
Type Aliases
// Exists: NONE!
// Missing:
type EitherTNec[F, A, B] = EitherT[F, NonEmptyChain[A], B]
type EitherTNel[F, A, B] = EitherT[F, NonEmptyList[A], B]
type EitherTNell[F, A, B] = EitherT[F, NonEmptyLazyList[A], B] // Scala 2.13+ Only
type EitherTNes[F, A, B] = EitherT[F, NonEmptySet[A], B]
type EitherTNeSeq[F, A, B] = EitherT[F, NonEmptySeq[A], B]
type EitherTNev[F, A, B] = EitherT[F, NonEmptyVector[A], B]
Ops
final case class EitherT[F[_], A, B](value: F[Either[A, B]]) {
// Exists:
def toValidatedNel(implicit F: Functor[F]): F[ValidatedNel[A, B]]
def toValidatedNec(implicit F: Functor[F]): F[ValidatedNec[A, B]]
def toNestedValidatedNel(implicit F: Functor[F]): Nested[F, ValidatedNel[A, *], B]
def toNestedValidatedNec(implicit F: Functor[F]): Nested[F, ValidatedNec[A, *], B]
// Missing:
def toValidatedNell(implicit F: Functor[F]): F[ValidatedNell[A, B]] // Scala 2.13+ Only
def toValidatedNes(implicit F: Functor[F], O: Order[A]): F[ValidatedNes[A, B]]
def toValidatedNeSeq(implicit F: Functor[F]): F[ValidatedNeSeq[A, B]]
def toValidatedNev(implicit F: Functor[F]): F[ValidatedNev[A, B]]
def toNestedValidatedNell(implicit F: Functor[F]): Nested[F, ValidatedNell[A, *], B] // Scala 2.13+ Only
def toNestedValidatedNes(implicit F: Functor[F], O: Order[A]): Nested[F, ValidatedNes[A, *], B]
def toNestedValidatedNeSeq(implicit F: Functor[F]): Nested[F, ValidatedNeSeq[A, *], B]
def toNestedValidatedNev(implicit F: Functor[F]): Nested[F, ValidatedNev[A, *], B]
}
Ior
Type Aliases
// Exists:
type IorNel[+B, +A] = Ior[NonEmptyList[B], A]
type IorNec[+B, +A] = Ior[NonEmptyChain[B], A]
type IorNes[B, +A] = Ior[NonEmptySet[B], A]
// Missing:
type IorNeSeq[+B, +A] = Ior[NonEmptySeq[B], A]
type IorNev[+B, +A] = Ior[NonEmptyVector[B], A]
type IorNell[+B, +A] = Ior[NonEmptyLazyList[B], A] // Scala 2.13+ Only
Object Ops
object Ior {
// Exists:
def bothNec[A, B](a: A, b: B): IorNec[A, B]
def bothNel[A, B](a: A, b: B): IorNel[A, B]
def leftNec[A, B](a: A): IorNec[A, B]
def leftNel[A, B](a: A): IorNel[A, B]
// Missing:
def bothNell[A, B](a: A, b: B): IorNell[A, B]
def bothNes[A, B](a: A, b: B)(implicit O: Order[A]): IorNes[A, B]
def bothNeSeq[A, B](a: A, b: B): IorNeSeq[A, B]
def bothNev[A, B](a: A, b: B): IorNev[A, B]
def leftNell[A, B](a: A): IorNell[A, B]
def leftNes[A, B](a: A)(implicit O: Order[A]): IorNes[A, B]
def leftNeSeq[A, B](a: A): IorNeSeq[A, B]
def leftNev[A, B](a: A): IorNev[A, B]
def rightNec[A, B](b: B): IorNec[A, B]
def rightNel[A, B](b: B): IorNel[A, B]
def rightNell[A, B](a: A): IorNell[A, B]
def rightNes[A, B](a: A)(implicit O: Order[A]): IorNes[A, B]
def rightNeSeq[A, B](a: A): IorNeSeq[A, B]
def rightNev[A, B](a: A): IorNev[A, B]
}
Ops
sealed abstract class Ior[+A, +B] extends Product with Serializable {
// Exists:
final def toIorNes[AA >: A](implicit O: Order[AA]): IorNes[AA, B]
final def toIorNec[AA >: A]: IorNec[AA, B]
final def toIorNel[AA >: A]: IorNel[AA, B]
// Missing:
final def toIorNell[AA >: A]: IorNell[AA, B]
final def toIorNeSeq[AA >: A]: IorNeSeq[AA, B]
final def toIorNev[AA >: A]: IorNev[AA, B]
}
Id Ops
final class IorIdOps[A](private val a: A) extends AnyVal {
// Exists: NONE!
// Missing:
def bothNec[B](b: B): IorNec[A, B]
def bothNel[B](b: B): IorNel[A, B]
def bothNell[B](b: B): IorNell[A, B]
def bothNes[B](b: B)(implicit O: Order[A]): IorNes[A, B]
def bothNeSeq[B](b: B): IorNeSeq[A, B]
def bothNev[B](b: B): IorNev[A, B]
def leftNec[B]: IorNec[A, B]
def leftNel[B]: IorNel[A, B]
def leftNell[B]: IorNell[A, B]
def leftNes[B](implicit O: Order[A]): IorNes[A, B]
def leftNeSeq[B]: IorNeSeq[A, B]
def leftNev[B]: IorNev[A, B]
def rightNec[B]: IorNec[B, A]
def rightNel[B]: IorNel[B, A]
def rightNell[B]: IorNell[B, A]
def rightNes[B](implicit O: Order[B]): IorNes[B, A]
def rightNeSeA[B]: IorNeSeq[B, A]
def rightNev[B]: IorNev[B, A]
}
IorT
Missing all instances Ne* methods and Aliases
Validated
Type Aliases
// Exists:
type ValidatedNel[+E, +A] = Validated[NonEmptyList[E], A]
type ValidatedNec[+E, +A] = Validated[NonEmptyChain[E], A]
// Missing:
type ValidatedNes[E, +A] = Validated[NonEmptySet[E], A]
type ValidatedNeSeq[+E, +A] = Validated[NonEmptySeq[E], A]
type ValidatedNev[+E, +A] = Validated[NonEmptyVector[E], A]
type ValidatedNell[+E, +A] = Validated[NonEmptyLazyList[E], A] // Scala 2.13+ Only
Object Ops
object Validated {
// Exists:
def condNec[A, B](test: Boolean, b: => B, a: => A): ValidatedNec[A, B]
def condNel[E, A](test: Boolean, a: => A, e: => E): ValidatedNel[E, A]
def invalidNec[A, B](a: A): ValidatedNec[A, B]
def invalidNel[E, A](e: E): ValidatedNel[E, A]
def validNec[A, B](b: B): ValidatedNec[A, B]
def validNel[E, A](a: A): ValidatedNel[E, A]
// Missing:
def condNes[A, B](test: Boolean, b: => B, a: => A)(implicit O: Order[A]): ValidatedNes[A, B]
def condNeSeq[A, B](test: Boolean, b: => B, a: => A): ValidatedNeSeq[A, B]
def condNev[A, B](test: Boolean, b: => B, a: => A): ValidatedNev[A, B]
def condNell[A, B](test: Boolean, b: => B, a: => A): ValidatedNell[A, B]
def invalidNes[A, B](a: A)(implicit O: Order[A]): ValidatedNes[A, B]
def invalidNeSeq[A, B](a: A): ValidatedNeSeq[A, B]
def invalidNev[A, B](a: A): ValidatedNev[A, B]
def invalidNell[A, B](a: A): ValidatedNell[A, B]
def validNes[A, B](b: B)(implicit O: Order[A]): ValidatedNes[A, B]
def validNeSeq[A, B](b: B): ValidatedNeSeq[A, B]
def validNev[A, B](b: B): ValidatedNev[A, B]
def validNell[A, B](b: B): ValidatedNell[A, B]
}
Ops
sealed abstract class Validated[+E, +A] extends Product with Serializable {
// Exists:
def toValidatedNel[EE >: E, AA >: A]: ValidatedNel[EE, AA]
def toValidatedNec[EE >: E, AA >: A]: ValidatedNec[EE, AA]
// Missing:
def toValidatedNes[EE >: E, AA >: A](implicit O: Order[A]): ValidatedNes[EE, AA]
def toValidatedNeSeq[EE >: E, AA >: A]: ValidatedNeSeq[EE, AA]
def toValidatedNev[EE >: E, AA >: A]: ValidatedNev[EE, AA]
def toValidatedNell[EE >: E, AA >: A]: ValidatedNell[EE, AA]
}
Id Ops
final class ValidatedIdSyntax[A](private val a: A) extends AnyVal {
// Exists:
def invalidNec[B]: ValidatedNec[A, B]
def invalidNel[B]: ValidatedNel[A, B]
def validNec[B]: ValidatedNec[B, A]
def validNel[B]: ValidatedNel[B, A]
// Missing:
def invalidNes[B](implicit O: Order[A]): ValidatedNes[A, B]
def invalidNeSeq[B]: ValidatedNeSeq[A, B]
def invalidNev[B]: ValidatedNev[A, B]
def invalidNell[B]: ValidatedNell[A, B]
def validNes[B](implicit O: Order[B]): ValidatedNes[B, A]
def validNeSeq[B]: ValidatedNeSeq[B, A]
def validNev[B]: ValidatedNev[B, A]
def validNell[B]: ValidatedNell[B, A]
}
List
Ops
final class ListOps[A](private val la: List[A]) extends AnyVal {
// Exists:
def groupByNec[B](f: A => B)(implicit B: Order[B]): SortedMap[B, NonEmptyChain[A]]
def groupByNel[B](f: A => B)(implicit B: Order[B]): SortedMap[B, NonEmptyList[A]]
def groupByNelA[F[_], B](f: A => F[B])(implicit F: Applicative[F], B: Order[B]): F[SortedMap[B, NonEmptyList[A]]]
// Included for completeness, but probably doesn't need other *Ne* methods.
def scanLeftNel[B](b: B)(f: (B, A) => B): NonEmptyList[B]
def scanRightNel[B](b: B)(f: (A, B) => B): NonEmptyList[B]
def toNel: Option[NonEmptyList[A]] = NonEmptyList.fromList(la)
// Missing:
def groupByNes[B](f: A => B)(implicit B: Order[B], A: Order[A]): SortedMap[B, NonEmptySet[A]]
def groupByNeSeq[B](f: A => B)(implicit B: Order[B]): SortedMap[B, NonEmptySeq[A]]
def groupByNev[B](f: A => B)(implicit B: Order[B]): SortedMap[B, NonEmptyVector[A]]
def groupByNell[B](f: A => B)(implicit B: Order[B]): SortedMap[B, NonEmptyLazyList[A]]
def groupByNesA[F[_], B](f: A => F[B])(implicit F: Applicative[F], B: Order[B]): F[SortedMap[B, NonEmptySet[A]]]
def groupByNeSeqA[F[_], B](f: A => F[B])(implicit F: Applicative[F], B: Order[B]): F[SortedMap[B, NonEmptySeq[A]]]
def groupByNevA[F[_], B](f: A => F[B])(implicit F: Applicative[F], B: Order[B]): F[SortedMap[B, NonEmptyVector[A]]]
def groupByNellA[F[_], B](f: A => F[B])(implicit F: Applicative[F], B: Order[B]): F[SortedMap[B, NonEmptyLazyList[A]]]
}
Option
Ops
final class OptionOps[A](private val oa: Option[A]) extends AnyVal {
// Exists:
def toInvalidNec[B](b: => B): ValidatedNec[A, B]
def toInvalidNel[B](b: => B): ValidatedNel[A, B]
def toLeftNec[B](b: => B): EitherNec[A, B]
def toLeftNel[B](b: => B): EitherNel[A, B]
def toRightNec[B](b: => B): EitherNec[B, A]
def toRightNel[B](b: => B): EitherNel[B, A]
def toValidNec[B](b: => B): ValidatedNec[B, A]
def toValidNel[B](b: => B): ValidatedNel[B, A]
// Missing:
def toLeftNesA[B](b: => B)(implicit O: Order[A]): EitherNesA[A, B]
def toLeftNeSeqA[B](b: => B): EitherNeSeqA[A, B]
def toLeftNevA[B](b: => B): EitherNevA[A, B]
def toLeftNellA[B](b: => B): EitherNellA[A, B]
def toRightNesA[B](b: => B)(implicit O: Order[B]): EitherNesA[B, A]
def toRightNeSeqA[B](b: => B): EitherNeSeqA[B, A]
def toRightNevA[B](b: => B): EitherNevA[B, A]
def toRightNellA[B](b: => B): EitherNellA[B, A]
def toInvalidNesA[B](b: => B(implicit O: Order[A])): ValidatedNesA[A, B]
def toInvalidNeSeqA[B](b: => B): ValidatedNeSeqA[A, B]
def toInvalidNevA[B](b: => B): ValidatedNevA[A, B]
def toInvalidNellA[B](b: => B): ValidatedNellA[A, B]
def toValidNesA[B](b: => B)(implicit O: Order[B]): ValidatedNesA[B, A]
def toValidNeSeqA[B](b: => B): ValidatedNeSeqA[B, A]
def toValidNevA[B](b: => B): ValidatedNevA[B, A]
def toValidNellA[B](b: => B): ValidatedNellA[B, A]
}
NonEmptyLazyList
Ops
class NonEmptyLazyListOps[A](private val value: NonEmptyLazyList[A])
extends AnyVal
with NonEmptyCollection[A, LazyList, NonEmptyLazyList] {
// Exists:
final def toNonEmptyVector: NonEmptyVector[A]
final def toNonEmptyList: NonEmptyList[A]
final def toNem[T, U](implicit ev: A <:< (T, U), order: Order[T]): NonEmptyMap[T, U]
final def toNes[B >: A](implicit order: Order[B]): NonEmptySet[B]
final def toNev[B >: A]: NonEmptyVector[B]
// Missing:
final def toNeSeq[B >: A]: NonEmptySeq[B]
}
Reducible
Ops
@typeclass trait Reducible[F[_]] extends Foldable[F] { self =>
// Exists:
def maximumByNel[A, B: Order](fa: F[A])(f: A => B): NonEmptyList[A]
def maximumNel[A](fa: F[A])(implicit A: Order[A]): NonEmptyList[A]
def minimumByNel[A, B: Order](fa: F[A])(f: A => B): NonEmptyList[A]
def minimumNel[A](fa: F[A])(implicit A: Order[A]): NonEmptyList[A]
def toNonEmptyList[A](fa: F[A]): NonEmptyList[A]
// Missing:
// All variations of above for
// Nec, Nev, Nes, NeSeq, NeLL
}
Syntax
trait Ops[F[_], A] extends Serializable {
type TypeClassType <: Reducible[F]
def self: F[A]
// Exists:
def minimumNel(implicit A: Order[A]): NonEmptyList[A]
def maximumNel(implicit A: Order[A]): NonEmptyList[A]
def minimumByNel[B](f: A => B)(implicit ev$1: Order[B]): NonEmptyList[A]
def maximumByNel[B](f: A => B)(implicit ev$1: Order[B]): NonEmptyList[A]
// Missing:
// All variations of above for
// Nec, Nev, Nes, NeSeq, NeLL
}
The reason why #3998 got stuck on my side was a very valid concern from @johnynek on whether we should keep stuffing collection wrappers with ad-hoc methods or try to generalize some (or most) of them for appropriate typeclasses (Traverse, NonEmptyTraverse, Foldable, Reducible, etc).
I agree with Oscar that generalization is better so I am not confident that it makes sense to merge #3998 until we elaborate some more generic solution.
@satorg Agreed. That's why I wanted to list out all the methods that we had now, and open a discussion on how we can improve the current situation. These methods wouldn't exist if there wasn't some utility, so I think any general replacement needs to be nearly as convenient. I proposed 2 solutions but those only cover a subset of the missing methods.
There is likely a way to split these missing methods into different categories and address the individually. But figured a single list to get started would help show the entire picture.
I guess there's a typo in the description: ValidatedNel is enlisted as "missing", but in fact it is not:
https://github.com/typelevel/cats/blob/main/core/src/main/scala/cats/data/package.scala#L5
@satorg Thanks, corrected.