diff --git a/core/shared/src/main/scala/zio/prelude/NonEmptyMap.scala b/core/shared/src/main/scala/zio/prelude/NonEmptyMap.scala new file mode 100644 index 000000000..f18f32681 --- /dev/null +++ b/core/shared/src/main/scala/zio/prelude/NonEmptyMap.scala @@ -0,0 +1,224 @@ +package zio.prelude + +/* + * Copyright 2020-2022 John A. De Goes and the ZIO Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import zio.NonEmptyChunk + +import scala.language.implicitConversions + +/** + * A non-empty wrapper for the scala immutable map. Note - this does not attempt to implement all features of + * map but what the author considers to be the "normal ones". + */ +final class NonEmptyMap[K, V] private (private val map: Map[K, V]) { self => + + private def newMap[V2](map: Map[K, V2]): NonEmptyMap[K, V2] = new NonEmptyMap(map) + + /** Converts this `NonEmptyMap` to a `Map`. */ + def toMap: Map[K, V] = map + + /** + * Returns an element of this `NonEmptyMap` and the remainder, which is a (possibly empty) `Map`. + */ + @inline + def peel: ((K, V), Map[K, V]) = (map.head, map.tail) + + /** + * Returns an element of this `NonEmptyMap` + * and the remainder or `None`, if the remainder is empty. + */ + def peelNonEmpty: ((K, V), Option[NonEmptyMap[K, V]]) = { + val (head, tail) = peel + if (tail.isEmpty) + (head, None) + else + (head, Some(newMap(tail))) + } + + /** + * Creates a new `NonEmptyMap` with an additional element, unless the element is + * already present. + * + * @param elem the element to be added + * @return a new map that contains all elements of this map and that also + * contains `elem`. + */ + def +(elem: (K, V)): NonEmptyMap[K, V] = newMap(map + elem) + + /** + * Creates a new `NonEmptyMap` by adding all elements contained in another collection to this `NonEmptyMap`, omitting duplicates. + * + * This method takes a collection of elements and adds all elements, omitting duplicates, into `NonEmptyMap`. + * + * Example: + * {{{ + * scala> val a = NonEmptyMap(1, 2) ++ NonEmptyMap(2, "a") + * a: zio.prelude.NonEmptyMap[Any] = NonEmptyMap(1, 2, a) + * }}} + * + * @param elems the collection containing the elements to add. + * @return a new `NonEmptyMap` with the given elements added, omitting duplicates. + */ + def ++(elems: Iterable[(K, V)]): NonEmptyMap[K, V] = newMap(map ++ elems) + + /** Adds the `elem` to this `NonEmptyMap`. Alias for `+`. */ + def add(elem: (K, V)): NonEmptyMap[K, V] = self + elem + + /** Removes the `elem` from this `NonEmptyMap`. Alias for `-`. */ + def remove(elem: K): Map[K, V] = map - elem + + /** + * Returns the tail of this `NonEmptyMap` if it exists or `None` otherwise. + */ + def tailNonEmpty: Option[NonEmptyMap[K, V]] = peelNonEmpty._2 + + /** + * Produces a new non empty map where values mapped according to function f. For compatibility + * does not use map.iew + */ + def mapValues[V1](f: V => V1): NonEmptyMap[K, V1] = { + val newInner = map.map(tup => tup._1 -> f(tup._2)) + newMap(newInner) + } + + override def hashCode: Int = map.hashCode ^ NonEmptyMap.NonEmptyMapSeed + + override def equals(that: Any): Boolean = + that match { + case that: AnyRef if self.eq(that) => true + case that: NonEmptyMap[_, _] => self.map == that.toMap + case _ => false + } + + override def toString: String = s"NonEmpty$map" +} + +object NonEmptyMap { + + def apply[K, V](elem: (K, V), others: Iterable[(K, V)]): NonEmptyMap[K, V] = + new NonEmptyMap(others.toMap + elem) + + /** + * Creates a `NonEmptyMap` with the specified elements. + * @tparam A the type of the `NonEmptyMap`'s elements + * @param elem an element of the created `NonEmptyMap` + * @param others the remaining elements of the created `NonEmptyMap` + * @return a new `NonEmptyMap` with elements `elem` and `others` + */ + def apply[K, V](elem: (K, V), others: (K, V)*): NonEmptyMap[K, V] = apply(elem, others) + + def unapply[K, V](arg: NonEmptyMap[K, V]): Some[((K, V), Map[K, V])] = Some(arg.peel) + + /** + * Constructs a `NonEmptyMap` from a `NonEmptyChunk`. + */ + def fromNonEmptyChunk[K, V](elems: NonEmptyChunk[(K, V)]): NonEmptyMap[K, V] = apply(elems.head, elems.tail) + + /** + * Constructs a `NonEmptyMap` from a `NonEmptyList`. + */ + def fromNonEmptyList[K, V](elems: NonEmptyList[(K, V)]): NonEmptyMap[K, V] = apply(elems.head, elems.tail) + + /** + * Constructs a `NonEmptyMap` from an element and `Map`. + */ + def fromMap[K, V](elem: (K, V), others: Map[K, V]): NonEmptyMap[K, V] = apply(elem, others) + + /** + * Constructs a `NonEmptyMap` from a `Map` or `None` otherwise. + */ + def fromMapOption[K, V](map: Map[K, V]): Option[NonEmptyMap[K, V]] = map.headOption.map(fromMap(_, map.tail)) + + /** + * Constructs a `NonEmptyMap` from an element and `Iterable`. + */ + def fromIterable[K, V](head: (K, V), tail: Iterable[(K, V)]): NonEmptyMap[K, V] = + fromMap(head, tail.toMap) + + /** + * Constructs a `NonEmptyMap` from an `Iterable` or `None` otherwise. + */ + def fromIterableOption[K, V](iterable: Iterable[(K, V)]): Option[NonEmptyMap[K, V]] = + iterable.headOption.fold(None: Option[NonEmptyMap[K, V]])(h => Some(fromIterable(h, iterable.tail))) + + /** + * Constructs a `NonEmptyMap` with the specified single value. + */ + def single[K, V](head: (K, V)): NonEmptyMap[K, V] = + NonEmptyMap(head) + + /** + * Provides an implicit conversion from `NonEmptyMap` to the `Map` + * for interoperability with Scala's collection library. + */ + implicit def toMap[K, V](nonEmptyMap: NonEmptyMap[K, V]): Map[K, V] = + nonEmptyMap.toMap + + private val NonEmptyMapSeed: Int = 1247820194 + + /** + * GroupByOption function returns an option of a nonEmpty map instead of a map because by definition + * the elements will be non-empty - returns None if from is + */ + def groupByOption[A, K](from: Iterable[A])(f: A => K): Option[NonEmptyMap[K, Iterable[A]]] = + from.headOption.map(_ => new NonEmptyMap(from.groupBy(f))) + + /** + * from a non-empty chunk we can create a non-empty map of non-empty chunks + */ + def groupByFromNonEmptyChunk[A, K](from: NonEmptyChunk[A])(f: A => K): NonEmptyMap[K, NonEmptyChunk[A]] = { + val gb = from.groupBy(f) + val asChunks = gb.map(p => (p._1 -> NonEmptyChunk.fromIterableOption(p._2).get)) // safe! + new NonEmptyMap(asChunks) + } + + /** + * from a non-empty set we can create a non-empty map of non-empty sets + */ + def groupByFromNonEmptySet[A, K](from: NonEmptySet[A])(f: A => K): NonEmptyMap[K, NonEmptySet[A]] = { + val gb = from.groupBy(f) + val asSets = gb.map(p => (p._1 -> NonEmptySet.fromIterableOption(p._2).get)) // safe! + new NonEmptyMap(asSets) + } + + /** + * from a non-empty list we can create a non-empty map of non-empty list + */ + def groupByFromNonEmptyList[A, K](from: NonEmptyList[A])(f: A => K): NonEmptyMap[K, NonEmptyList[A]] = { + val gb = from.groupBy(f) + val asLists = gb.map(p => (p._1 -> NonEmptyList.fromIterableOption(p._2).get)) // safe! + new NonEmptyMap(asLists) + } + +} + +trait NonEmptyMapSyntax { + implicit final class NonEmptyMapIterableOps[K, V](private val iterable: Iterable[(K, V)]) { + + /** + * Constructs a `NonEmptyMap` from an `Iterable` or `None` otherwise. + */ + def toNonEmptyMap: Option[NonEmptyMap[K, V]] = NonEmptyMap.fromIterableOption(iterable) + } + implicit final class NonEmptyMapMapOps[K, V](self: Map[K, V]) { + + /** + * Constructs a `NonEmptyMap` from a `Map` or `None` otherwise. + */ + def toNonEmptyMap: Option[NonEmptyMap[K, V]] = NonEmptyMap.fromMapOption(self) + } +} diff --git a/core/shared/src/main/scala/zio/prelude/NonEmptySet.scala b/core/shared/src/main/scala/zio/prelude/NonEmptySet.scala index 32fdc53fc..57b4d0ce4 100644 --- a/core/shared/src/main/scala/zio/prelude/NonEmptySet.scala +++ b/core/shared/src/main/scala/zio/prelude/NonEmptySet.scala @@ -95,6 +95,15 @@ final class NonEmptySet[A] private (private val set: Set[A]) { self => /** Removes the `elem` from this `NonEmptySet`. Alias for `-`. */ def remove(elem: A): Set[A] = set - elem + /** + * removes the elem from `NonEmptySet`, returning Some(NonEmptySet) if there's anything + * left, otherwise None + */ + def removeNonEmpty(elem: A): Option[NonEmptySet[A]] = { + val newSet = set - elem + if (newSet.nonEmpty) Some(new NonEmptySet(set)) else None + } + /** * Returns the tail of this `NonEmptySet` if it exists or `None` otherwise. */ diff --git a/core/shared/src/main/scala/zio/prelude/NonEmptySortedMap.scala b/core/shared/src/main/scala/zio/prelude/NonEmptySortedMap.scala new file mode 100644 index 000000000..487570a2e --- /dev/null +++ b/core/shared/src/main/scala/zio/prelude/NonEmptySortedMap.scala @@ -0,0 +1,197 @@ +package zio.prelude + +/* + * Copyright 2020-2022 John A. De Goes and the ZIO Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import zio.NonEmptyChunk + +import scala.collection.immutable.SortedMap +import scala.language.implicitConversions +import scala.math.{Ordering => SOrdering} + +/** + * A non-empty wrapper for the scala immutable map. Note - this does not attempt to implement all features of + * map but what the author considers to be the "normal ones". + */ +final class NonEmptySortedMap[K, V] private (private val map: SortedMap[K, V])(implicit sOrdering: SOrdering[K]) { + self => + + private def newMap[V2](map: SortedMap[K, V2]): NonEmptySortedMap[K, V2] = new NonEmptySortedMap(map) + + /** Converts this `NonEmptySortedMap` to a `SortedMap`. */ + def toMap: SortedMap[K, V] = map + + /** + * Returns an element of this `NonEmptySortedMap` and the remainder, which is a (possibly empty) `SortedMap`. + */ + @inline + def peel: ((K, V), SortedMap[K, V]) = (map.head, map.tail) + + /** + * Returns an element of this `NonEmptySortedMap` + * and the remainder or `None`, if the remainder is empty. + */ + def peelNonEmpty: ((K, V), Option[NonEmptySortedMap[K, V]]) = { + val (head, tail) = peel + if (tail.isEmpty) + (head, None) + else + (head, Some(newMap(tail))) + } + + /** + * Creates a new `NonEmptySortedMap` with an additional element, unless the element is + * already present. + * + * @param elem the element to be added + * @return a new map that contains all elements of this map and that also + * contains `elem`. + */ + def +(elem: (K, V)): NonEmptySortedMap[K, V] = newMap(map + elem) + + /** + * Creates a new `NonEmptySortedMap` by adding all elements contained in another collection to this `NonEmptySortedMap`, omitting duplicates. + * + * This method takes a collection of elements and adds all elements, omitting duplicates, into `NonEmptySortedMap`. + * + * Example: + * {{{ + * scala> val a = NonEmptySortedMap(1, 2) ++ NonEmptySortedMap(2, "a") + * a: zio.prelude.NonEmptySortedMap[Any] = NonEmptySortedMap(1, 2, a) + * }}} + * + * @param elems the collection containing the elements to add. + * @return a new `NonEmptySortedMap` with the given elements added, omitting duplicates. + */ + def ++(elems: Iterable[(K, V)]): NonEmptySortedMap[K, V] = newMap(map ++ elems) + + /** Adds the `elem` to this `NonEmptySortedMap`. Alias for `+`. */ + def add(elem: (K, V)): NonEmptySortedMap[K, V] = self + elem + + /** Removes the `elem` from this `NonEmptySortedMap`. Alias for `-`. */ + def remove(elem: K): SortedMap[K, V] = map - elem + + /** + * Returns the tail of this `NonEmptySortedMap` if it exists or `None` otherwise. + */ + def tailNonEmpty: Option[NonEmptySortedMap[K, V]] = peelNonEmpty._2 + + /** + * Produces a new non empty map where values mapped according to function f. + */ + def mapValues[V1](f: V => V1): NonEmptySortedMap[K, V1] = { + val newInner = map.map(p => (p._1, f(p._2))) + newMap(newInner) + } + + override def hashCode: Int = map.hashCode ^ NonEmptySortedMap.NonEmptyMapSeed + + override def equals(that: Any): Boolean = + that match { + case that: AnyRef if self.eq(that) => true + case that: NonEmptySortedMap[_, _] => self.map == that.toMap + case _ => false + } + + override def toString: String = s"NonEmpty$map" +} + +object NonEmptySortedMap { + + def apply[K, V](elem: (K, V), others: Iterable[(K, V)])(implicit sOrdering: SOrdering[K]): NonEmptySortedMap[K, V] = + new NonEmptySortedMap(SortedMap(others.toList: _*) + elem) + + /** + * Creates a `NonEmptySortedMap` with the specified elements. + * @tparam A the type of the `NonEmptySortedMap`'s elements + * @param elem an element of the created `NonEmptySortedMap` + * @param others the remaining elements of the created `NonEmptySortedMap` + * @return a new `NonEmptySortedMap` with elements `elem` and `others` + */ + def apply[K, V](elem: (K, V), others: (K, V)*)(implicit sOrdering: SOrdering[K]): NonEmptySortedMap[K, V] = + apply(elem, others) + + def unapply[K, V](arg: NonEmptySortedMap[K, V]): Some[((K, V), SortedMap[K, V])] = Some(arg.peel) + + /** + * Constructs a `NonEmptySortedMap` from a `NonEmptyChunk`. + */ + def fromNonEmptyChunk[K, V](elems: NonEmptyChunk[(K, V)])(implicit sOrdering: SOrdering[K]): NonEmptySortedMap[K, V] = + apply(elems.head, elems.tail) + + /** + * Constructs a `NonEmptySortedMap` from a `NonEmptyList`. + */ + def fromNonEmptyList[K, V](elems: NonEmptyList[(K, V)])(implicit sOrdering: SOrdering[K]): NonEmptySortedMap[K, V] = + apply(elems.head, elems.tail) + + /** + * Constructs a `NonEmptySortedMap` from an element and `SortedMap`. + */ + def fromMap[K, V](elem: (K, V), others: SortedMap[K, V])(implicit sOrdering: SOrdering[K]): NonEmptySortedMap[K, V] = + apply(elem, others) + + /** + * Constructs a `NonEmptySortedMap` from a `SortedMap` or `None` otherwise. + */ + def fromMapOption[K, V](map: SortedMap[K, V])(implicit sOrdering: SOrdering[K]): Option[NonEmptySortedMap[K, V]] = + map.headOption.map(fromMap(_, map.tail)) + + /** + * Constructs a `NonEmptySortedMap` from an `Iterable` or `None` otherwise. + */ + def fromIterableOption[K, V](iterable: Iterable[(K, V)])(implicit + sOrdering: SOrdering[K] + ): Option[NonEmptySortedMap[K, V]] = + iterable.headOption.fold(None: Option[NonEmptySortedMap[K, V]])(h => Some(apply(h, iterable.tail))) + + /** + * Constructs a `NonEmptySortedMap` with the specified single value. + */ + def single[K, V](head: (K, V))(implicit sOrdering: SOrdering[K]): NonEmptySortedMap[K, V] = + NonEmptySortedMap(head) + + /** + * Provides an implicit conversion from `NonEmptySortedMap` to the `SortedMap` + * for interoperability with Scala's collection library. + */ + implicit def toMap[K, V](nonEmptyMap: NonEmptySortedMap[K, V]): SortedMap[K, V] = + nonEmptyMap.toMap + + private val NonEmptyMapSeed: Int = 1147820194 + +} + +trait NonEmptySortedMapSyntax { + implicit final class NonEmptySortedMapIterableOps[K, V](private val iterable: Iterable[(K, V)])(implicit + sOrdering: SOrdering[K] + ) { + + /** + * Constructs a `NonEmptySortedMap` from an `Iterable` or `None` otherwise. + */ + def toNonEmptyMap: Option[NonEmptySortedMap[K, V]] = + NonEmptySortedMap.fromIterableOption(iterable) + } + implicit final class NonEmptySortedMapMapOps[K, V](self: SortedMap[K, V])(implicit sOrdering: SOrdering[K]) { + + /** + * Constructs a `NonEmptySortedMap` from a `SortedMap` or `None` otherwise. + */ + def toNonEmptyMap: Option[NonEmptySortedMap[K, V]] = + NonEmptySortedMap.fromMapOption(self) + } +} diff --git a/core/shared/src/main/scala/zio/prelude/NonEmptySortedSet.scala b/core/shared/src/main/scala/zio/prelude/NonEmptySortedSet.scala new file mode 100644 index 000000000..76fb70f25 --- /dev/null +++ b/core/shared/src/main/scala/zio/prelude/NonEmptySortedSet.scala @@ -0,0 +1,244 @@ +/* + * Copyright 2020-2022 John A. De Goes and the ZIO Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package zio.prelude + +import zio.NonEmptyChunk +import zio.prelude.coherent.HashPartialOrd + +import scala.collection.immutable.{SortedSet, TreeSet} +import scala.language.implicitConversions +import scala.math.{Ordering => SOrdering} + +final class NonEmptySortedSet[A] private (private val set: SortedSet[A]) { self => + + /** Converts this `NonEmptySortedSet` to a `SortedSet`. */ + def toSet: SortedSet[A] = set + + /** + * Returns an element of this `NonEmptySortedSet` and the remainder, which is a (possibly empty) `SortedSet`. + */ + @inline + def peel: (A, SortedSet[A]) = (set.head, set.tail) + + /** + * Returns an element of this `NonEmptySortedSet` + * and the remainder or `None`, if the remainder is empty. + */ + def peelNonEmpty: (A, Option[NonEmptySortedSet[A]]) = { + val (head, tail) = peel + if (tail.isEmpty) + (head, None) + else + (head, Some(new NonEmptySortedSet(tail))) + } + + /** + * Converts this `NonEmptySortedSet` to a `NonEmptyChunk`. + */ + def toNonEmptyChunk: NonEmptyChunk[A] = peel match { case (head, tail) => NonEmptyChunk.fromIterable(head, tail) } + + /** + * Converts this `NonEmptySortedSet` to a `NonEmptyList`. + */ + def toNonEmptyList: NonEmptyList[A] = peel match { case (head, tail) => NonEmptyList.fromIterable(head, tail) } + + /** + * Creates a new `NonEmptySortedSet` with an additional element, unless the element is + * already present. + * + * @param elem the element to be added + * @return a new set that contains all elements of this set and that also + * contains `elem`. + */ + def +(elem: A): NonEmptySortedSet[A] = new NonEmptySortedSet(set + elem) + + /** + * Computes the union between of `NonEmptySortedSet` and another set. + * + * @param that the set to form the union with. + * @return a new `NonEmptySortedSet` consisting of all elements that are in this + * set or in the given set `that`. + */ + def union(that: SortedSet[A]): NonEmptySortedSet[A] = new NonEmptySortedSet(set.union(that)) + + /** + * Creates a new `NonEmptySortedSet` by adding all elements contained in another collection to this `NonEmptySortedSet`, omitting duplicates. + * + * This method takes a collection of elements and adds all elements, omitting duplicates, into `NonEmptySortedSet`. + * + * Example: + * {{{ + * scala> val a = NonEmptySortedSet(1, 2) ++ NonEmptySortedSet(2, "a") + * a: zio.prelude.NonEmptySortedSet[Any] = NonEmptySortedSet(1, 2, a) + * }}} + * + * @param elems the collection containing the elements to add. + * @return a new `NonEmptySortedSet` with the given elements added, omitting duplicates. + */ + def ++(elems: Iterable[A]): NonEmptySortedSet[A] = new NonEmptySortedSet(set ++ elems) + + /** Adds the `elem` to this `NonEmptySortedSet`. Alias for `+`. */ + def add(elem: A): NonEmptySortedSet[A] = self + elem + + /** Removes the `elem` from this `NonEmptySortedSet`. Alias for `-`. */ + def remove(elem: A): SortedSet[A] = set - elem + + /** + * removes the elem from `NonEmptySortedSet`, returning Some(NonEmptySortedSet) if there's anything + * left, otherwise None + */ + def removeNonEmpty(elem: A): Option[NonEmptySortedSet[A]] = { + val newSet = set - elem + if (newSet.nonEmpty) Some(new NonEmptySortedSet(set)) else None + } + + /** + * Returns the tail of this `NonEmptySortedSet` if it exists or `None` otherwise. + */ + def tailNonEmpty: Option[NonEmptySortedSet[A]] = peelNonEmpty._2 + + def map[B](f: A => B)(implicit sOrdering: SOrdering[B]): NonEmptySortedSet[B] = new NonEmptySortedSet(set.map(f)) + + override def hashCode: Int = set.hashCode ^ NonEmptySortedSet.NonEmptySortedSetSeed + + override def equals(that: Any): Boolean = + that match { + case that: AnyRef if self.eq(that) => true + case that: NonEmptySortedSet[_] => self.set == that.toSet + case _ => false + } + + override def toString: String = s"NonEmpty$set" +} + +object NonEmptySortedSet { + def apply[A](elem: A, others: Iterable[A])(implicit ordering: SOrdering[A]): NonEmptySortedSet[A] = { + val treeSet = new TreeSet() + elem ++ others + new NonEmptySortedSet(treeSet) + } + + def apply[A](elem: A, others: A*)(implicit ordering: SOrdering[A]): NonEmptySortedSet[A] = + apply(elem, others) + + def unapply[A](arg: NonEmptySortedSet[A]): Some[(A, SortedSet[A])] = Some(arg.peel) + + /** + * Constructs a `NonEmptyChunk` from a `NonEmptyList`. + */ + def fromNonEmptyChunk[A](elems: NonEmptyChunk[A])(implicit ordering: SOrdering[A]): NonEmptySortedSet[A] = + apply(elems.head, elems.tail) + + /** + * Constructs a `NonEmptySortedSet` from a `NonEmptyList`. + */ + def fromNonEmptyList[A](elems: NonEmptyList[A])(implicit ordering: SOrdering[A]): NonEmptySortedSet[A] = + apply(elems.head, elems.tail) + + /** + * Constructs a `NonEmptySortedSet` from an element and `SortedSet`. + */ + def fromSet[A](elem: A, others: SortedSet[A])(implicit ordering: SOrdering[A]): NonEmptySortedSet[A] = + apply(elem, others) + + /** + * Constructs a `NonEmptySortedSet` from a `SortedSet` or `None` otherwise. + */ + def fromSetOption[A](set: SortedSet[A])(implicit ordering: SOrdering[A]): Option[NonEmptySortedSet[A]] = + set.headOption.map(fromSet(_, set.tail)) + + /** + * Constructs a `NonEmptySortedSet` from an `Iterable` or `None` otherwise. + */ + def fromIterableOption[A](iterable: Iterable[A])(implicit ordering: SOrdering[A]): Option[NonEmptySortedSet[A]] = + iterable.headOption.fold(None: Option[NonEmptySortedSet[A]])(h => Some(apply(h, iterable.tail))) + + /** + * Constructs a `NonEmptySortedSet` with the specified single value. + */ + def single[A](head: A)(implicit ordering: SOrdering[A]): NonEmptySortedSet[A] = + apply(head) + + /** Creates a `NonEmptySortedSet` containing elements from `l` and `r` */ + def union[A](l: NonEmptySortedSet[A], r: SortedSet[A])(implicit ordering: SOrdering[A]): NonEmptySortedSet[A] = { + val (head, tail) = l.peel + NonEmptySortedSet.fromSet(head, tail.union(r)) + } + + /** Creates a `NonEmptySortedSet` containing elements from `l` and `r` */ + def union[A](l: SortedSet[A], r: NonEmptySortedSet[A])(implicit ordering: SOrdering[A]): NonEmptySortedSet[A] = + union(r, l) + + /** + * The `Commutative` and `Idempotent` (and thus `Associative`) instance for `NonEmptySortedSet`. + */ + implicit def NonEmptySortedSetCommutativeIdempotent[A](implicit + aOrd: SOrdering[A] + ): Commutative[NonEmptySortedSet[A]] with Idempotent[NonEmptySortedSet[A]] = + new Commutative[NonEmptySortedSet[A]] with Idempotent[NonEmptySortedSet[A]] { + override def combine(l: => NonEmptySortedSet[A], r: => NonEmptySortedSet[A]): NonEmptySortedSet[A] = + l union r.toSet + } + + /** + * Derives a `Debug[NonEmptySortedSet[A]]` given a `Debug[A]`. + */ + implicit def NonEmptySortedSetDebug[A: Debug]: Debug[NonEmptySortedSet[A]] = + chunk => + Debug.Repr.VConstructor(List("zio", "prelude"), "NonEmptySortedSet", chunk.toNonEmptyList.map(_.debug).toCons) + + /** + * The `DeriveEqual` instance for `NonEmptySortedSet`. + */ + implicit val NonEmptySortedSetDeriveEqual: DeriveEqual[NonEmptySortedSet] = + new DeriveEqual[NonEmptySortedSet] { + def derive[A: Equal]: Equal[NonEmptySortedSet[A]] = + NonEmptySortedSetHashPartialOrd + } + + /** + * Derives a `Hash[NonEmptySortedSet[A]]` and `PartialOrd[NonEmptySortedSet[A]]` (and thus `Equal[NonEmptyList[A]]`) instance. + */ + implicit def NonEmptySortedSetHashPartialOrd[A]: Hash[NonEmptySortedSet[A]] with PartialOrd[NonEmptySortedSet[A]] = + HashPartialOrd.derive[Set[A]].contramap(_.toSet) + + /** + * Provides an implicit conversion from `NonEmptySortedSet` to the `Set` + * for interoperability with Scala's collection library. + */ + implicit def toSet[A](nonEmptySet: NonEmptySortedSet[A]): Set[A] = + nonEmptySet.toSet + + private val NonEmptySortedSetSeed: Int = 1247120194 + +} + +trait NonEmptySortedSetSyntax { + implicit final class NonEmptySortedSetIterableOps[A](private val iterable: Iterable[A])(implicit aOrd: SOrdering[A]) { + + /** + * Constructs a `NonEmptySortedSet` from an `Iterable` or `None` otherwise. + */ + def toNonEmptySortedSet: Option[NonEmptySortedSet[A]] = NonEmptySortedSet.fromIterableOption(iterable) + } + implicit final class NonEmptySortedSetSetOps[A](self: SortedSet[A])(implicit aOrd: SOrdering[A]) { + + /** + * Constructs a `NonEmptySortedSet` from a `Set` or `None` otherwise. + */ + def toNonEmptySortedSet: Option[NonEmptySortedSet[A]] = NonEmptySortedSet.fromSetOption(self) + } +}