Search code examples
scalacompiler-errorspartialfunction

Scala applying a PartialFunction with () is not the same as .apply()


I'm trying to refactor my scala code in a project (Play Framework 2.4) when I came with this idea:

(To provide a minimal working example, I've changed some classes, for instance, I have changed Result and Future[Result] with Int and Option[Int] respectively)

object ParFuncApply {

  trait CanBeAuthenticatedRequest[A]
  trait AuthenticatedRequest[A] extends CanBeAuthenticatedRequest[A]
  trait UnauthenticatedRequest[A] extends CanBeAuthenticatedRequest[A]


  private def fold[T](authenticated: (AuthenticatedRequest[_]) => T)
                             (unauthenticated: (UnauthenticatedRequest[_]) => T):
  PartialFunction[CanBeAuthenticatedRequest[_], T] = {
    case ar: AuthenticatedRequest[_] => authenticated(ar)
    case ur: UnauthenticatedRequest[_] => unauthenticated(ur)
  }

  def apply(request: CanBeAuthenticatedRequest[_])
           (authenticated: (AuthenticatedRequest[_]) => Int)
           (unauthenticated: (UnauthenticatedRequest[_]) => Int): Int = {
    fold(authenticated)(unauthenticated)(request)
  }

  def async(request: CanBeAuthenticatedRequest[_])
           (authenticated: (AuthenticatedRequest[_]) => Option[Int])
           (unauthenticated: (UnauthenticatedRequest[_]) => Option[Int]): Option[Int] = {
    fold(authenticated)(unauthenticated)(request)
  }
}

The code above compiles.

Then I though: I should restrict fold[T] parametrized types to Int and Option[Int], so I added:

object ParFuncApply {

  trait CanBeAuthenticatedRequest[A]
  trait AuthenticatedRequest[A] extends CanBeAuthenticatedRequest[A]
  trait UnauthenticatedRequest[A] extends CanBeAuthenticatedRequest[A]

  sealed trait Helper[T]

  object Helper {
    implicit object FutureResultHelper extends Helper[Option[Int]]
    implicit object ResultHelper extends Helper[Int]
  }

  private def fold[T: Helper](authenticated: (AuthenticatedRequest[_]) => T)
                             (unauthenticated: (UnauthenticatedRequest[_]) => T):
  PartialFunction[CanBeAuthenticatedRequest[_], T] = {
    case ar: AuthenticatedRequest[_] => authenticated(ar)
    case ur: UnauthenticatedRequest[_] => unauthenticated(ur)
  }

  def apply(request: CanBeAuthenticatedRequest[_])
           (authenticated: (AuthenticatedRequest[_]) => Int)
           (unauthenticated: (UnauthenticatedRequest[_]) => Int): Int = {
    fold(authenticated)(unauthenticated)(request)
  }

  def async(request: CanBeAuthenticatedRequest[_])
           (authenticated: (AuthenticatedRequest[_]) => Option[Int])
           (unauthenticated: (UnauthenticatedRequest[_]) => Option[Int]): Option[Int] = {
    fold(authenticated)(unauthenticated)(request)
  }
}

But this code does no longer compile, instead, if I change: fold(authenticated)(unauthenticated)(request) to fold(authenticated)(unauthenticated).apply(request) (I have added an explicit call to apply()) it compiles. Why is this happening? calling () and .apply() on a class should be the same, isn't it?

The compiler seems to be asking for the return type (Int or Option[Int]) to be passed to the PartialFunction instead of a type of CanBeAuthenticatedRequest.


Solution

  • Because you define a context bound in `fold[T : Helper]' the compiler will add another parameter list. In other words a context bound is just syntactic sugar for:

    private def fold[T](authenticated: (AuthenticatedRequest[_]) => T)
                       (unauthenticated: (UnauthenticatedRequest[_]) => T)
                       (implicit helper: Helper[T): PartialFunction[CanBeAuthenticatedRequest[_], T] 
    

    so when you call

    fold(authenticated)(unauthenticated)(request)
    

    the compiler thinks request is supposed to be an explicitly specified implicit Helper[T].