def nonEmpty[T, A <: IterableOnce[T]](iterableOnce: A, message: String = ""): A =
if (iterableOnce.iterator.nonEmpty)
iterableOnce
else
throw new IllegalArgumentException(StringUtils.trimToNull(message))
When I try to pass a list of Strings:
def foo(list: List[String]): Unit = nonEmpty(list)
I get the following error: inferred type arguments [Nothing,List[String]] do not conform to method nonEmpty’s type parameter bounds [T,A <: IterableOnce[T]]
I have trouble to understand why I get error and how I need to modify nonBlank to make it generic for all subtypes fof IterableOnce.
The T type parameter is not linked to any argument of your method. You just require that there is some T and that A is some IterableOnce of T. So the compiler happily infers the bottom type for T as that’s a subtype for all types.
def nonEmpty[A <: IterableOnce[_]](iterableOnce: A, message: String = ""): A =
if (iterableOnce.iterator.nonEmpty)
iterableOnce
else
throw new IllegalArgumentException(message)
Uhm is hard to explain without knowing what exactly is confusing.
I will try to go step by step, I hope I cover everything without being too tedious.
If I miss something, please let me know so I could try to explain that in more detail.
First, let’s see the goal: “Be able to accept any collection-like value”.
Then let’s see what that means:
Traditional C[x] looking types that inherit from IterableOnce[x], like List[x] or Set[x]
Types with different shapes that also inherit from IterableOnce[x], like Map[k, v] or BitSet
Types that actually do not inherit fromIterableOnce[x] but that behave like collections, like Array[x] or String
For 1 & 2, we conclude that we actually need a proper type C rather than a type constructor C[x]
And for 3, we conclude that we need a typeclass rather than subtyping.
Now, the question would be: “Does such typeclass already exists in the stdlib?”
The answer is yes, it is IsIterableOnce; from the name and the Scaladoc we can conclude that its presence means that the type C can be view as an IterablceOnce. AS we can see it only provides an apply method that will turn our C into an IterableOnce, it also has a path-dependant type A which represents the type of the elements inside the collection.
So: acceptsAnyIterable[C] means the method is generic on C col: Cit accepts a value of type C implicit ev: IsIterableOnce[C] we require the presence of IsIterable for that type C; or in other words, we require that such type behaves as an IterableOnce Option[ev.A] the return type is an Option of the elements inside the collection, such type is dependent on the evidence, and in turn, is dependant on the provided value. If we pass a List[Int] then A = Int if we pass a BitSet then A = Int if we pass a String then A = Char. ev(col).iterator using the evidence we turn the col into an IterableOnce[A] and then we get an Iterator[A] from it.