vumaasha
vumaasha

Reputation: 2845

Scala: please explain the generics involved

Could some one please explain the generics involved in the following code from play framework

class AuthenticatedRequest[A, U](val user: U, request: Request[A]) extends WrappedRequest[A](request)

class AuthenticatedBuilder[U](userinfo: RequestHeader => Option[U],
        onUnauthorized: RequestHeader => Result = _ => Unauthorized(views.html.defaultpages.unauthorized()))
          extends ActionBuilder[({ type R[A] = AuthenticatedRequest[A, U] })#R]

The ActionBuilder actualy has type R[A], it is getting reassigned, this much I understand. please explain the intricacies of the syntax

Upvotes: 1

Views: 200

Answers (1)

Steve Waldman
Steve Waldman

Reputation: 14083

The bit that's confusing you is called a "type lambda". If you search for "scala type lambda", you'll find lots of descriptions and explanations. See e.g. here, from which I'm drawing a lot of inspiration. (Thank you Bartosz Witkowski!)

To describe it very simply, you can think of it as a way to provide a default argument to a type constructor. I know, huh?

Let's break that down. If we have...

trait Unwrapper[A,W[_]] {
  /* should throw an Exception if we cannot unwrap */
  def unwrap( wrapped : W[A] ) : A 
}

You could define an OptionUnwrapper easily enough:

class OptionUnwrapper[A] extends Unwrapper[A,Option] {
  def unwrap( wrapped : Option[A] ) : A = wrapped.get
}

But what if we want to define an unwrapper for the very similar Either class, which takes two type parameters [A,B]. Either, like Option, is often used as a return value for things that might fail, but where you might want to retain information about the failure. By convention, "success" results in a Right object containing a B, while failure yields a Left object containing an A. Let's make an EitherUnwrapper, so we have an interface in common with Option to unwrap these sorts of failable results. (Potentially even useful!)

class EitherUnwrapper[A,B] extends Unwrapper[B,Either] { // unwrap to a successful result of type B
  def unwrap( wrapped : Either[A,B] ) : B = wrapped match {
     case Right( b ) => b // we ignore the left case, allowing a MatchError
  }
}

This is conceptually fine, but it doesn't compile! Why not? Because the second parameter of Unwrapper was W[_], that is a type that accepts just one parameter. How can we "adapt" Either's type constructor to be a one parameter type? If we needed a version of an ordinary function or constructor with fewer arguments, we might supply default arguments. So that's exactly what we'll do.

class EitherUnwrapper[A,B] extends Unwrapper[B,({type L[C] = Either[A,C]})#L] { 
  def unwrap( wrapped : Either[A,B] ) : B = wrapped match {
     case Right( b ) => b 
  }
}

The type alias part

type L[C] = Either[A,C]

adapts Either into a type that requires just one type parameter rather than two, by supplying A as a default first type parameter. But unfortunately, scala doesn't allow you to define type aliases just anywhere: they have to live in a class, trait, or object. But if you define the trait in an enclosing scope, you might not have access to the default value you need for type A! So, the trick is to define a throwaway inner class in a place where A is defined, just where you need the new type.

A set of curly braces can (depending on context) be interpreted as a type definition in scala, for a structural type. For instance in...

def destroy( rsrc : { def close() } ) = rsrc.close()

...the curly brace defines a structural type meaning any object with a close() function. Structural types can also include type aliases.

So { type L[C] = Either[A,C] } is just the type of any object that contains the type alias L[C]. To extract an inner type from an enclosing type -- rather than an enclosing instance -- in Scala, we have to use a type projection rather than a dot. The syntax for a type projection is EnclosingType#InnerType. So, we have { type L[C] = Either[A,C] }#L. For reasons that elude me, the Scala compiler gets confused by that, but if we put the type definition in parentheses, everything works, so we have ({ type L[C] = Either[A,C] })#L.

Which is pretty precisely analogous to ({ type R[A] = AuthenticatedRequest[A, U] })#R in your question. ActionBuilder needs to be parameterized with a type that takes one parameter. AuthenticatedRequest takes two parameters. To adapt AuthenticatedRequest into a type suitable for ActionBuilder, U is provided as a default parameter in the type lambda.

Upvotes: 5

Related Questions