Skip to content

Missing NonEmpty Collection Helper methods #4070

Description

@zarthross

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.

  • NonEmptyChain
  • NonEmptyLazyList
  • NonEmptyList
  • NonEmptyMap (Doesn't seem to be used for any 'left' sides so not included below)
  • NonEmptySeq
  • NonEmptySet
  • NonEmptyVector

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 the Bifunctor syntax/ops.
  • Add a OneOf typeclass (sort of thing) of the form trait OneOf[F[_], A]{ def one(a: A): F[A]} and make a add a generic leftLift method on Bifunctor.
    • OneOf differs from Pure in 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
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions