As I am working a design model, I am torn between two different methods of indicating a parameter of type List
must be nonEmpty
. I began by using List[Int]
with an accompanying require
statement to verify the List
is nonEmpty
.
case class A(name: String, favoriteNumbers: List[Int]) {
require(favoriteNumbers.nonEmpty, "favoriteNumbers must not be empty")
}
I then needed to make the list optional. If the List
is provided, it must be nonEmpty
. I'm using using Option[List[Int]]
with an accompanying require
statement to verify, if the Option
is nonEmpty
, the list must also be nonEmpty
.
case class B(name: String, favoriteNumbers: Option[List[Int]]) {
require(
favoriteNumbers.isEmpty || favoriateNumbers.get.nonEmpty
, "when defined, favoriteNumbers.get must be nonEmpty"
)
}
However, I need to use this non-empty List
all over the system I am modeling. This means that my code has these same require
statements duplicated everywhere. Is there a (non-ScalaZ) way to have a new type, say NeList
, which is defined and behaves identically to List, with the only change being an exception is thrown when NeList
attempts to be instantiated with no elements?
I tried to Google for this and couldn't find a set of search terms to hone on this area. I either got really simple List
how-tos, or all sorts of references to ScalaZ's NEL (Non Empty List). So, if there is a link out there that would help with this, I would love to see it.
An empty list has no tail. This is an exceptional case. The tail of a non-empty list is that part of the list following the first element. This function has type 'a list -> 'a list.
The isEmpty operation is utilized to check if the stated list is empty or not. Here, m1 is Map name. isEmpty is method which returns true if the stated list is empty else it returns false.
If you
def foo[A](x: ::[A]) = "List has length "+x.length
then you insist that the list be nonempty. But of course your lists are all typed as List
, so you need a helper method to give you a nonempty 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
}
}
}
Now you can safely apply the operation to get an Option
out. (You could also run side-effecting functions in a foreach-like method.)
Now, this is rather non-idiomatic Scala. But it is safe at compile time (the @unchecked
notwithstanding--Scala's compiler isn't quite smart enough to realize that the type parameter hasn't changed).
You could implement a non-empty list yourself with implicit conversions between List[A] and 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
Then you can define your functions where this is needed such that they take a Nel[A] as a parameter:
def f(l: Option[Nel[String]]) = { ... }
And call them with normal lists (assuming the implicit defs are in scope):
f(Some(List("hello", "world")) // works
f(Some(Nil)) // throws IllegalArgumentException
f(None) // works
EDIT: It should be noted that this does not provide compile time guarantees that the List[A] passed in will not be empty. If that's what you want, then get rid of the implicit def list2Nel
and require clients of your function to pass in an Nel[A] explicitly, thus guaranteeing at compile time that the list is not empty.
Also, this is a very basic NonEmptyList implementation. A more complete solution is found in scalaz (granted it was specifically requested in the question that scalaz not be used): https://github.com/scalaz/scalaz/blob/series/7.2.x/core/src/main/scala/scalaz/NonEmptyList.scala
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With