4

Some time ago I've started using Cats and found OptionT very useful to work with Future[Option[T]] in most cases. But I faced with one drawback, to use AplicativeError I need to define type alias type FutureOption[T] = OptionT[Future, X] to matching F[_] required by AplicativeError and explicitly specify the type of my expression as FutureOption[T].

type FutureOption[T] = OptionT[Future, T] // definition to match F[_] kind

val x = OptionT.liftF(Future.failed(new Exception("error"))) : FutureOption[String] // need to specify type explicitly
x.recover {
  case NonFatal(e) => "fixed"
}

If I remove type definition and explicit type specification of my expression the recover will not be available because OptionT[Future, T] don't match F[_], so it can't be converted implicitly to AplicativeErrorOps.

Unfortunately, the example below won't work because there is no recover method.

val x = OptionT.liftF(Future.failed(new Exception("error")))
x.recover {
  case NonFatal(e) => "fixed"
}

Is there any way to avoid such kind of boilerplate code? At least I want to avoid specifying expression types as FutureOption[T] explicitly.

2 Answers2

4

In addition to the other answer, I would like to suggest that you make sure you have -Ypartial-unification enabled for your build.

This is a fix for partial unification of type constructors. You can find a more detailed explanation about the fix here.

With partial unification enabled the code you provided in your question compiles fine. Please note that if you're using an IDE (e.g. Intellij) you might get "false negatives" (the code is underlined as incorrect and code completion doesn't work), but the scalac/sbt/gradle will compile it just fine.

Denis Rosca
  • 3,409
  • 19
  • 38
  • Thanks a lot! It was disabled. Now compiles fine without explicit types. Unfortunately, idea highlights the code as incorrect. – andrey.feoktistov Feb 23 '18 at 14:43
  • 1
    Unfortunately IDEA still doesn't support partial unification correctly. You can go to https://youtrack.jetbrains.com/issue/SCL-11320 and vote to make the issue a higher priority. – Denis Rosca Feb 24 '18 at 14:52
0

Yes, there are at least two ways to cope with the type ascription.

  • using type lambdas (this can be intimidating):

    val a: { type λ[A] = OptionT[Future, A] }#λ
    
  • using a compiler plugin like kind-projector, example usage:

    val a: Lambda[A => OptionT[Future, A]]
    

But if you wanted to call Future's recover, you can always do:

val x = OptionT.liftF(Future.failed(new Exception("error")))
x.value.recover ...
mpetruska
  • 633
  • 3
  • 6
  • Thank you for the advice! I read about the kind-projector, but it's still not quite clear for me how to use it in this case. Could you please provide some more detailed example. As about `x.value.recover ...`, I know this option, but I prefer to stay in `OptionT` monad because I have other calculations in it. – andrey.feoktistov Feb 23 '18 at 11:27
  • As the [kind-projector documentation](https://github.com/non/kind-projector) states, you can use the type annotations `OptionT[Future, ?]` and `λ[A => OptionT[Future, A]]` if you enable kind-projector inside your build. – mpetruska Feb 23 '18 at 14:20