什么是最佳方式(不使用Scalaz)键入需要非空列表?

cha*_*ium 14 scala list

当我正在设计模型时,我在两种不同的指示类型参数List必须的方法之间徘徊nonEmpty.我开始使用List[Int]附带的require声明来验证ListnonEmpty.

case class A(name: String, favoriteNumbers: List[Int]) {
  require(favoriteNumbers.nonEmpty, "favoriteNumbers must not be empty")
}
Run Code Online (Sandbox Code Playgroud)

然后我需要使列表可选.如果List提供,它必须是nonEmpty.我正在使用Option[List[Int]]附带的require声明来验证,如果OptionnonEmpty,则列表也必须是nonEmpty.

case class B(name: String, favoriteNumbers: Option[List[Int]]) {
  require(
      favoriteNumbers.isEmpty || favoriateNumbers.get.nonEmpty
    , "when defined, favoriteNumbers.get must be nonEmpty"
  )
}
Run Code Online (Sandbox Code Playgroud)

但是,我需要List在我正在建模的系统中使用这个非空的.这意味着我的代码在require任何地方都有相同的语句重复.是否有一种(非ScalaZ)方式来获得一个新类型,比如说NeList,它定义并且行为与List相同,只有在NeList尝试实例化没有元素的情况下才会引发异常?

我试图谷歌为此而找不到一组搜索术语来磨练这个领域.我要么非常简单List,要么对ScalaZ的NEL(非空列表)进行各种引用.所以,如果有一个链接可以帮助解决这个问题,我很乐意看到它.

Rex*_*err 9

如果你

def foo[A](x: ::[A]) = "List has length "+x.length
Run Code Online (Sandbox Code Playgroud)

然后你坚持认为清单是非空的.但是当然你的列表都是打字的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
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

现在您可以安全地应用该操作来获取Option.(您也可以在类似foreach的方法中运行副作用函数.)

现在,这是非惯用的Scala.但它在编译时是安全的(@unchecked尽管如此 - Scala的编译器还不够智能,无法实现类型参数没有改变).

  • @ chaotic3quilibrium语法是`type NonEmptyList = ::`. (4认同)

gre*_*ghz 5

您可以使用 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
Run Code Online (Sandbox Code Playgroud)

然后您可以在需要的地方定义您的函数,以便它们将 Nel[A] 作为参数:

def f(l: Option[Nel[String]]) = { ... }
Run Code Online (Sandbox Code Playgroud)

并使用普通列表调用它们(假设隐式 defs 在范围内):

f(Some(List("hello", "world")) // works
f(Some(Nil)) // throws IllegalArgumentException
f(None) // works
Run Code Online (Sandbox Code Playgroud)

编辑:应该注意,这不提供编译时保证传入的 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

  • 在完全无异常的条件下在运行时因异常而失败的隐式转换非常可怕。 (9认同)
  • @MarcelKrcah - 因为创建非空列表的重点是将不变量放在类型中,而不是在运行时对其进行断言。 (3认同)
  • @MarcelKrcah require 的主要问题是在运行之前您不知道代码有问题。在需要 NonEmptyList 的情况下,您是说运行时错误比编译时错误好。给定选项,我将始终选择编译时错误。如果输入必须始终是非空列表,请在使用的类型中对其进行编码。 (2认同)