Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the optimal way (not using Scalaz) to type require a non-empty List?

Tags:

list

scala

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.

like image 501
chaotic3quilibrium Avatar asked Jan 29 '15 20:01

chaotic3quilibrium


People also ask

What is the tail of an empty list?

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.

Is list empty Scala?

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.


2 Answers

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).

like image 121
Rex Kerr Avatar answered Oct 12 '22 13:10

Rex Kerr


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

like image 30
gregghz Avatar answered Oct 12 '22 15:10

gregghz