user811602
user811602

Reputation: 1354

How to return from Scala method with generics

I have two trait

trait TResponseData extends Serializable {} //all response data
trait TRequestData extends Serializable {} //all request

Now I have Response class, which contains data and request

case class Response[A <: TResponseData, B <: TRequestData](data: A, request: B = null){}

While instance of case class Response must have data, request is not mandatory. So I declared its default to null in case class constructor. From Scala console, I am able to create its instance like

Response(null, null)
Response(null)

To generate instance of Response, I have created helper method:

def generateResponse[A <: TResponseData, B <: TRequestData] (data: A, request: B, passRequestInResponse: Boolean): Response[A, B] = {
    if (passRequestInResponse) 
        return Response(data, request)
    return Response(data, null)
}

Based on boolean passRequestInResponse, I am setting request in Response. On compiling same, I am getting follow error:

error: type mismatch;
 found   : Null(null)
 required: B
           return Response(data, null)

How can I achieve same (making request optional in generateResponse method)

Upvotes: 0

Views: 103

Answers (3)

Assaf Mendelson
Assaf Mendelson

Reputation: 12991

The reason for the error is that all types in Scala have a bottom type of Nothing. Nothing cannot accept the "null" value and therefore the type definition is problematic as B can be Nothing.

To solve this you can add a lower bound:

 def generateResponse[A <: TResponseData, B >: Null <: TRequestData ] (data: A, request: B,
                                                           passRequestInResponse: Boolean): Response[A, B] = {

this will mean that Nothing is an illegal type for B as the minimum allowed in Null.

The use of Option[B] is probably a better idea and should be used in this case in term of good design, however, in more generic cases there are situations where lower bound would be better.

P.S. you shouldn't be using return. As you can see in other answers you can do:

if (passRequestInResponse) {
  Response(data, Some(request))
} else {
  Response(data)
}

as this will give the value. Return is actually a control flow breaking (it is implemented with an exception) and can have unintended consequences. See for example https://blog.knoldus.com/scala-best-practices-say-no-to-return/

Upvotes: 1

Tim
Tim

Reputation: 27356

Using null is a bad idea (as is return) so it is better to use Option:

trait TResponseData extends Serializable //all response data
trait TRequestData extends Serializable //all request

case class Response[A <: TResponseData, B <: TRequestData](data: A, request: Option[B]=None)

def generateResponse[A <: TResponseData, B <: TRequestData] (data: A, request: B, passRequestInResponse: Boolean): Response[A, B] =
  if (passRequestInResponse) {
    Response(data, Some(request))
  } else {
    Response(data)
  }

You can also do some Option magic like this:

def generateResponse[A <: TResponseData, B <: TRequestData] (data: A, request: B, passRequestInResponse: Boolean): Response[A, B] =
  Response(
    data,
    Some(request).filter(_ => passRequestInResponse)
  )

The advantage of this formulation is that it will work if request is null, and will treat this case as if passRequestInResponse was false

Update

As noted by @jwvh it is not clear that the generateResponse function is useful because you can just call Response(data, Some(request)) or Response(data) as needed. If you don't like that Some in the first version, then create a class object with a custom apply method:

object Response {
  def apply[A <: TResponseData, B <: TRequestData](data: A, request: B): Response[A, B]
    = Response(data, Option(request))
}

Then you can just use Response(data, request). This will work correctly if request is null.

Upvotes: 2

jwvh
jwvh

Reputation: 51271

You can either modify the generateResponse() return type...

def generateResponse[A <: TResponseData, B <: TRequestData] (data: A
                                                            ,request: B
                                                            ,passRequestInResponse: Boolean
                                                            ): Response[A, _] = 
  if (passRequestInResponse) Response(data, request)
  else                       Response(data, null)

... or you can cast the null to the required type.

def generateResponse[A <: TResponseData, B <: TRequestData] (data: A
                                                            ,request: B
                                                            ,passRequestInResponse: Boolean
                                                            ): Response[A, B] = 
  if (passRequestInResponse) Response(data, request)
  else                       Response(data, null.asInstanceOf[B])

(But don't use return. It's not idiomatic Scala.)

In general, I don't really see why you need generateResponse() at all. The Response constructor will still either have a B or it won't, so it should be an Option[B] that defaults to None. (Idiomatic Scala avoids null.)

Upvotes: 2

Related Questions