Подтвердить что ты не робот

Каков оптимальный способ (не используя 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). Итак, если есть связь, которая поможет с этим, я бы с удовольствием это увидел.

4b9b3361

Ответ 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