embracing-nondeterminism-code/src/main/scala/green/thisfieldwas/embracingnondeterminism/data/NonEmptyList.scala

121 lines
3.7 KiB
Scala

package green.thisfieldwas.embracingnondeterminism.data
/** The `NonEmptyList` class encodes a dimension contextualized by the presence
* of an unknown number of instances of term `A`. The length of the
* `NonEmptyList` in practices is nondeterministic at runtime without specific
* knowledge of the inputs to the operation that produces it. In contrast with
* `List`, a `NonEmptyList` is guaranteed to always hold at least one instance
* of term `A` in its `head`.
*
* This `NonEmptyList` is modeled as a special case of a cons: a typed, present
* construction of a head and a tail of a potentially empty `List`.
*
* As at least one instance of the term `A` is guaranteed to be present, this
* context is always in the desired case.
*
* @param head
* The present instance of term `A`.
* @param tail
* A potentially empty remainder of the list.
* @tparam A
* The type contained within the `NonEmptyList`.
*/
case class NonEmptyList[+A](head: A, tail: List[A]) {
/** The length of the `NonEmptyList`. This will always be at least 1 and is a
* linear-time operation.
*
* @return
* The length of the `NonEmptyList`.
*/
def length: Int = 1 + tail.length
/** Conses a new head to this `NonEmptyList`. The resulting `NonEmptyList`
* should be treated as though its length is guaranteed to only be at least
* 1. This is a constant-time operation.
*
* @param newHead
* The new head.
* @tparam B
* The type of the head.
* @return
* The new `NonEmptyList`.
*/
def |:[B >: A](newHead: B): NonEmptyList[B] = NonEmptyList(newHead, head :: tail)
/** Converts the `NonEmptyList` to a `List`. The resulting `List` should be
* treated as though it may be empty. This is a constant-time operation.
*
* @return
* The new `List`.
*/
def toList: List[A] = head :: tail
/** Converts the `NonEmptyList` to a Scala `Seq`. This is a linear-time
* operation.
*
* @return
* The new `Seq`.
*/
def toSeq: Seq[A] = head +: tail.toSeq
}
object |: {
/** Allows for idiomatic pattern matching against a `NonEmptyList`:
*
* {{{
* nonEmptyList match {
* head |: tail => ???
* }
* }}}
*
* @param nel
* The `NonEmptyList` to unapply.
* @tparam A
* The type contained within the `NonEmptyList`.
* @return
* A present 2-tuple of the head and tail.
*/
def unapply[A](nel: NonEmptyList[A]): scala.Some[(A, List[A])] = scala.Some((nel.head, nel.tail))
}
object NonEmptyList {
/** Allows for the idiomatic construction of a `NonEmptyList` using this
* syntax:
*
* {{{
* NonEmptyList(1, 2, 3, 4, ...)
* }}}
*
* @param head
* @param tail
* @tparam A
* @return
*/
def apply[A](head: A, tail: A*): NonEmptyList[A] = NonEmptyList(head, List(tail: _*))
implicit val nonEmptyListFunctor: Functor[NonEmptyList] = new Functor[NonEmptyList] {
/** Given a structure `F[A]` and function `f: A => B`, if the structure is
* in the desired case, then apply the function such that `F[A] => F[B]`.
* If the structure `F[A]` is in the undesired case, then propagate the
* undesired case as `F[B]`.
*
* @param fa
* The instance of the structure to apply the function to.
* @param f
* The function to apply, if the structure is in the desired case.
* @tparam A
* The type of instances contained within the structure.
* @tparam B
* The type to map the instances into.
* @return
* The resulting structure.
*/
def map[A, B](fa: NonEmptyList[A])(f: A => B): NonEmptyList[B] =
f(fa.head) |: Functor[List].map(fa.tail)(f)
}
}