0

Let's say I come up with a combinator:

def optional[M[_]: Applicative, A, B](fn: Kleisli[M, A, B]) =
  Kleisli[M, Option[A], Option[B]] {
    case Some(t) => fn(t).map(_.some)
    case None => Applicative[M].point(none[B])
  }

This combinator maps any Kleisli[M, A, B] to Kleisli[M, Option[A], Option[B].

However, after some time, I realize (admittedly with the help of estewei on #scalaz) this can be made to work with containers more general than just Option, namely anything for which there is a Traverse instance:

def traverseKleisli[M[_]: Applicative, F[_]: Traverse, A, B](k: Kleisli[M, A, B]) =
  Kleisli[M, F[A], F[B]](k.traverse)

so that optional can now be defined as:

def optional[M[_]: Applicative, A, B](fn: Kleisli[M, A, B]) =
  traverseKleisli[M, Option, A, B](fn)

However, I'd like to verify that at least the resulting type signature is equal to the original definition of optional, and whereas I could resort to hovering over both definitions in my IDE (Ensime in my case) and compare the response, I'd like a more solid way of determining that.

I tried:

implicitly[optional1.type =:= optional2.type]

but (obviously?) that fails due to both identifies being considered unstable by scalac.

Other than perhaps temporarily making both of the functions objects with an apply method, are there any easy ways to compare their static types without resorting to relying on hints from IDE presentation compilers?

P.S. the name optional comes from the fact that I use that combinator as part of a validation DSL to take a Kleisli-wrapped Validation[String, T] and turn it into a Kleisli-wrapped Validation[String, Option[T]] that verifies the validity of optional values if present.

Erik Kaplun
  • 37,128
  • 15
  • 99
  • 111

1 Answers1

1

The problem you're having is that a method is not a value in scala, and values are monotyped. You can test that a particular "instance" of your method has the correct type (using a utility function from shapeless):

val optional1Fix = optional1[Future, Int, String] _
val optional2Fix = optional2[Future, Int, String] _
import shapeless.test._
sameTyped(optional1Fix)(optional2Fix)

but as with unit tests, this is somewhat unsatisfying as even if we test several instances we can't be sure it works for everything. (Note that implicitly[optional1Fix.type =:= optional2Fix.type] still doesn't work, I assume because the compiler never realizes when two path-dependent types are equal).

For a full solution we have to see the complete function as a value, so we would have to replace it with an object with an apply method as you suggest (analogous to shapeless' ~>). The only alternative I can think of is a macro, which would have to be applied to the object containing the methods, and know which methods you wanted to compare; writing a specific macro for this specific test seems a little excessive.

lmm
  • 17,386
  • 3
  • 26
  • 37