I have a question very similar to this one: Scala higher kinded type variance
This, however is slightly different in that, well, it doesn't compile (scala 2.11.8).
The basic idea is to take a provided array of "things". If the array is null, return a default value of some type (e.g. Boolean
, Option
, List[Int]
), otherwise do work on the array and produce a result. The result and default value have the same type.
The challenge I'm having is getting this to work across a broad set of result types.
Here's a contrived example:
trait NullGuard[F[_]] {
def nullGuard[A, B](arr: Array[A], default: F[B])(expr: => F[B]): F[B] =
if (arr == null || arr.length == 0) default else expr
}
Let's create an implementation that returns an Option:
implicit def optionNullGuard[F[X] <: Option[X]]: NullGuard[F] = new NullGuard[F]() {}
The above does compile, but the following attempt to use the above type class does not:
def returnsOption[F[_], A, B](arr: Array[A])(implicit ng: NullGuard[F]): Option[B] = {
ng.nullGuard(arr, None) {
// sample work
if (arr.length % 2 == 0) Option(1) else None
}
}
I get the following compile error:
type mismatch;
found : None.type
required: F[?]
ng.nullGuard(arr, None){
How can I get this to work? I'm also open to another approach, if there is one.
Since your typeclass does not have any abstract methods, it can be replaced by a single polymorphic nullGuard
method:
def nullGuard[A, B]
(arr: Array[A], defaultValue: B)
(processArray: Array[A] => B)
: B = if (arr == null || arr.isEmpty) defaultValue else processArray(arr)
The higher kinded type parameter F
also seems no longer necessary: it doesn't cost you anything to provide a method that works for any B
as return type, instead of just F[B]
.
Here is your contrived, slightly modified example: extracting the last value from an array if it has an even number of elements:
for (example <- List[Array[Int]](null, Array(), Array(42), Array(1, 42))) {
val lastAtEvenIndex = nullGuard[Int, Option[Int]](example, Some(0)) {
a => if (a.size % 2 == 0) Option(a.last) else None
}
println(lastAtEvenIndex)
}
Output:
Some(0)
Some(0)
None
Some(42)
It returns None
for arrays of uneven length, and treats empty/null arrays as if they had 0
as the "last" element.
Full example as a single code snippet with None
as default value:
def nullGuard[A, B]
(arr: Array[A], defaultValue: B)
(processArray: Array[A] => B)
: B = if (arr == null || arr.isEmpty) defaultValue else processArray(arr)
for (example <- List[Array[Int]](null, Array(), Array(42), Array(1, 42))) {
val lastAtEvenIndex = nullGuard[Int, Option[Int]](example, None) {
a => if (a.size % 2 == 0) Option(a.last) else None
}
println(lastAtEvenIndex)
}
prints:
None
None
None
Some(42)