Каков оптимальный способ (не используя Scalaz) для ввода типа непустого списка?
Когда я работаю над моделью проекта, я разрываю между двумя разными способами указания параметра типа List
должен быть nonEmpty
. Я начал с использования List[Int]
с сопроводительным оператором require
, чтобы проверить, что List
есть nonEmpty
.
case class A(name: String, favoriteNumbers: List[Int]) {
require(favoriteNumbers.nonEmpty, "favoriteNumbers must not be empty")
}
Затем мне нужно было сделать список опционным. Если указан List
, он должен быть nonEmpty
. Я использую Option[List[Int]]
с сопроводительным оператором require
для проверки, если Option
является nonEmpty
, список также должен быть nonEmpty
.
case class B(name: String, favoriteNumbers: Option[List[Int]]) {
require(
favoriteNumbers.isEmpty || favoriateNumbers.get.nonEmpty
, "when defined, favoriteNumbers.get must be nonEmpty"
)
}
Однако мне нужно использовать этот непустой List
всю систему, которую я моделирую. Это означает, что мой код имеет те же самые require
утверждения, которые дублируются повсюду. Есть ли способ (не-ScalaZ) иметь новый тип, скажем NeList
, который определен и ведет себя идентично List, при этом единственным изменением является исключение, когда NeList
пытается создать экземпляр без элементов?
Я попробовал Google для этого и не смог найти набор условий поиска, чтобы отточить эту область. Я либо получил очень простые List
инструкции, либо всевозможные ссылки на ScalaZ NEL (Non Empty List). Итак, если есть связь, которая поможет с этим, я бы с удовольствием это увидел.
Ответы
Ответ 1
Если вы
def foo[A](x: ::[A]) = "List has length "+x.length
то вы настаиваете на том, чтобы список был непустым. Но, конечно, все ваши списки напечатаны как List
, поэтому вам нужен вспомогательный метод, чтобы дать вам непустой список:
implicit class NonEmptyList[A](private val underlying: List[A]) {
def ifNonEmpty[B](f: ::[A] => B): Option[B] = {
underlying match {
case x: ::[A @unchecked] => Some(f(x))
case _ => None
}
}
}
Теперь вы можете безопасно применить операцию, чтобы получить Option
. (Вы также можете запускать побочные эффекты в методе, подобном foreach).
Теперь это скорее неидиоматический Scala. Но это безопасно во время компиляции (@unchecked
несмотря на то, что - Scala компилятор недостаточно умен, чтобы понять, что параметр типа не изменился).
Ответ 2
Вы можете реализовать непустой список самостоятельно с неявными преобразованиями между List [A] и Nel [A]:
case class Nel[A](val head: A, val tail: List[A] = Nil)
implicit def list2Nel[A](list: List[A]): Nel[A] = {
require(!list.isEmpty)
Nel(list.head, list.tail)
}
implicit def nel2List[A](nel: Nel[A]): List[A] = nel.head :: nel.tail
Затем вы можете определить свои функции там, где это необходимо, чтобы они взяли Nel [A] в качестве параметра:
def f(l: Option[Nel[String]]) = { ... }
И назовите их обычными списками (предполагая, что неявные defs находятся в области):
f(Some(List("hello", "world")) // works
f(Some(Nil)) // throws IllegalArgumentException
f(None) // works
EDIT: Следует отметить, что это не гарантирует время компиляции, которое прошел List List [A], не будет пустым. Если это то, что вы хотите, затем избавиться от implicit def list2Nel
и потребовать от клиентов вашей функции передать в Nel [A] явно, тем самым гарантируя во время компиляции, что список не пуст.
Кроме того, это очень простая реализация NonEmptyList. Более полное решение найдено в scalaz (при условии, что он был специально запрошен в вопросе, что scalaz не используется): https://github.com/scalaz/scalaz/blob/series/7.2.x/core/src/main/scala/scalaz/NonEmptyList.scala