tmccarthy / tmm-scala-collections

Some collections I find useful

Version Matrix

tmm-scala-collections

CircleCI Maven Central

This is a set of libraries that contain Scala collections and collection-oriented utilities that I've found useful. It was previously a sub-project of tmccarthy/tmmUtils, but it has reached a level of maturity that justifies splitting it out.

Usage

The libraries have been published to Maven central. They can be used by adding the following to your build.sbt

val tmmCollectionsVersion = "0.0.1"

libraryDependencies += "au.id.tmm.tmm-scala-collections" %% "tmm-scala-collections-core"       % tmmCollectionsVersion,
libraryDependencies += "au.id.tmm.tmm-scala-collections" %% "tmm-scala-collections-circe"      % tmmCollectionsVersion,
libraryDependencies += "au.id.tmm.tmm-scala-collections" %% "tmm-scala-collections-cats"       % tmmCollectionsVersion,
libraryDependencies += "au.id.tmm.tmm-scala-collections" %% "tmm-scala-collections-scalacheck" % tmmCollectionsVersion % Test, 

Collections

The following collections are provided by the tmm-scala-collections-core artefact:

  • DupelessSeq: A (poorly-named) collection which can be thought of as a sequence without duplicates, and with a constant-time contains check. It differs from ListSet (and other sets that retain insertion order) in that element order is considered for equality.
  • NonEmptyDupelessSeq: A DupelessSeq that is guaranteed to be non-empty.
  • NonEmptySet: A set that is guaranteed to be non-empty. Unlike the NonEmptySet provided by Cats, this collection relies on universal equality in the same way as the Scala set. This makes it easier to use, as elements do not require an Order instance, but comes at the cost of strict-lawfulness.
  • NonEmptyMap: A map that is guaranteed to be non-empty. Again, like NonEmptySet, this differs from the Cats NonEmptyMap in that it relies on universal equality rather than requiring an Order instance for keys.
  • NonEmptyArraySeq: An immutable ArraySeq that is guaranteed to be non-empty.

Third-party library integration

The other modules in this project provide integration between the above collections and some popular FP libraries:

  • tmm-scala-collections-scalacheck provides integration with ScalaCheck to support property-based testing.
  • tmm-scala-collections-circe provides codecs for use with the Circe json library.
  • tmm-scala-collections-cats provides extensive lawful and unlawful instances for the collections above.

Syntax

import au.id.tmm.collections.syntax._

The tmm-scala-collections-core project provides a few useful syntax extensions via the above import. Some highlights are outlined below:

Safe groupBy

The groupBy method provided by the IterableOps class can be improved by reflecting the non-empty nature of the groups in the type signature. The safeGroupBy syntax provides this:

import au.id.tmm.collections.NonEmptySet
import au.id.tmm.collections.syntax._
import au.id.tmm.collections.cats.instances.all._
import cats.data.NonEmptyList

val list = List("apple", "apricot", "banana")
val set = Set("apple", "apricot", "banana")

// ❌ BAD, return type doesn't indicate that groups cannot be empty
val _: Map[Char, List[String]] = list.groupBy(_.head)
val _: Map[Char, Set[String]]  = set.groupBy(_.head)

// ✅ GOOD, return type indicates that groups are non-empty
val _: Map[Char, NonEmptyList[String]] = list.safeGroupBy(_.head)
val _: Map[Char, NonEmptySet[String]]  = set.safeGroupBy(_.head)

orError operations for Iterable

The orError syntaxes allow for easily extracting elements from Iterable collections based on the number of elements. I have found this valuable when processing and cleaning data from external sources:

  • onlyElementOrException returns a Right containing the element if the collection has exactly one element, Left otherwise.
  • emptyOrException returns Right(()) if the collection is empty, Left otherwise
  • atMostOne returns Right(None) for empty collections, Right(Some) for those with one element, and Left for two or more elements.