3

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.

Andrey Tyukin
  • 43,673
  • 4
  • 57
  • 93
Erik
  • 997
  • 4
  • 14
  • 24
  • What is the `C[X] <: Option[X]` good for? Do you expect to have any strict subclasses of `Option` in your code? (there should be only `None` and `Some` anyway, because the trait is sealed). – Andrey Tyukin Jun 29 '18 at 22:06
  • I was using that as an attempt to return `Some` or `None`. I'm pretty new at this so I'm not sure what is required, and what isn't, to make this work – Erik Jun 29 '18 at 22:08
  • The `A <: Array[A]` also looks somewhat mysterious. Could you maybe elaborate what exactly you wanted to achieve with that? – Andrey Tyukin Jun 29 '18 at 22:10
  • 1
    I think it could have been written as `def nullGuard[A, B](arr: Array[A], default: Res[B])...` . (I updated my sample code above to reflect this) – Erik Jun 29 '18 at 22:11

1 Answers1

5

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)
Andrey Tyukin
  • 43,673
  • 4
  • 57
  • 93
  • 1
    @Erik of course it does; added full example with `None` default value, you can test it on [Scastie](https://scastie.scala-lang.org/). – Andrey Tyukin Jun 29 '18 at 23:01