Basically, this function returns a list of flights for an airline. But if the airline doesn't exist, I want to throw some custom runtime exception. If that airline does exist, I want to return a list of flights (which could potentially be empty).
Is this is the standard way of doing things in Scala, or is there a better way to write this code?
Coming from a Java background, I'm still trying to get to grips with the Scala way of doing things.
def getFlightsForAirline(id:Long):Try[Option[List[Flight]]] = {
val maybeAirline:Option[Airline] = Airline.getById(id)
maybeAirline match {
case Some(airline) => {
Success(Flight.getFlightsByAirline(airline))
}
case None => Failure(new RecordNotFoundException("Couldnt find airline"))
}
}
2 Answers 2
The
maybeAirline
variable isn't necessary. Note that the type annotation in the declaration is optional, and can easily be inferred by the compiler. So I would change that to:def getFlightsForAirline(id: Long): Try[Option[List[Flight]]] = Airline.getById(id) match { case Some(airline) => Success(Flight.getFlightsByAirline(airline)) case None => Failure(new RecordNotFoundException("Couldnt find airline")) }
Notice that the Scala style guide suggests putting a space after the colon in a type annotation –
name: Type
instead ofname:Type
.Every time you destructure an
Option
withmatch/case
, they kill a puppy because you should have probably usedmap
. Well, almost every time, because theFailure
is an important part of this function's behaviour. But this still is somewhat of a code smell.The return type
Try[Option[List[Flight]]]
strikes me as rather weird: What is the difference betweenSuccess(Some(List()))
andSuccess(None())
? I guess the latter might occur ifgetFlightsByAirline
isn't passed a valid airline.Try
,Option
andList
are essentially the same thing (either you get a result, or there isn't anything there: Failure, None or empty List). Therefore it does not make much sense to nest them, and it would be better to return aTry[List[Flight]]
. This could be implemented along the lines of:def getFlightsForAirline(id: Long): Try[List[Flight]] = Airline.getById(id) flatMap { Flight.getFlightsByAirline(_) } match { case Some(flights) => Success(flights) case None => Failure(new RecordNotFoundException("Couldnt find airline")) }
This way, the
Failure
is returned if either of thegetXByY
methods returnsNone
.Interestingly, there is no builtin way to transform an
Option
to aTry
(along the lines ofmaybe toTry { MyException("Reason") }
) while there is atry.toOption
method.Your
RecordNotFoundException
will have a much more Scala-ish vibe if you use acase class
here. This way, you can omit thenew
to create a new instance, and just write:Failure(RecordNotFoundException("..."))
-
\$\begingroup\$ +1 for the
Option[List[...]]
type, seen it too many times where it doesn't make sense :). Also how about consideringSeq
instead ofList
? \$\endgroup\$Akos Krivachy– Akos Krivachy2013年12月14日 15:21:52 +00:00Commented Dec 14, 2013 at 15:21 -
1\$\begingroup\$ Good. You might want to comment on the clarity of
getFlightsForAirline
andgetFlightsByAirline
as well. It is not obvious which you would like to use and how either of those would behave. \$\endgroup\$ScalaWilliam– ScalaWilliam2013年12月14日 18:19:34 +00:00Commented Dec 14, 2013 at 18:19 -
\$\begingroup\$ You can use a
map
here instead of aflatMap
, cannot you? \$\endgroup\$scand1sk– scand1sk2013年12月16日 07:52:50 +00:00Commented Dec 16, 2013 at 7:52 -
\$\begingroup\$ @scand1sk No I cannot, that is the point of this exercise. Using Haskell's notation:
map :: (A → B) → M[A] → M[B]
andfmap :: (A → M[B]) → M[A] → M[B]
. Translated into Scala, the former produces two nestedOption
s:Option[Flight].map(Airline => Option[List[Flight]]): Option[Option[List[Flight]]]
, whereasOption[Flight].flatMap(Airline => Option[List[Flight]]): Option[List[Flight]]
. Inmap
, the thing inside the output container is returned. InflatMap
, instances of the output container are returned, which are then concatenated to produce the final output. \$\endgroup\$amon– amon2013年12月16日 08:58:04 +00:00Commented Dec 16, 2013 at 8:58 -
\$\begingroup\$ You are right, I did not realized that
getFlightsForAirLine
returned anOption
. I have seen cases whereSome(Nil)
have not the same semantics asNone
, but this one ought not to be such a case. \$\endgroup\$scand1sk– scand1sk2013年12月16日 09:43:22 +00:00Commented Dec 16, 2013 at 9:43
The most idiomatic way of using Option
is to use map/flatMap
methods:
def getFlightsForAirline(id: Long): Try[List[Flight]] =
Airline.getById(id) flatMap Flight.getFlightsByAirline map Success.apply getOrElse {
Failure(new RecordNotFoundException("Couldnt find airline"))
}
Note: this works, if you simplify returned type, e.g strip Option
.
Otherwise, you need just change flatMap
to map
:
def getFlightsForAirline(id: Long): Try[Option[List[Flight]]] =
Airline.getById(id) map Flight.getFlightsByAirline map Success.apply getOrElse {
Failure(new RecordNotFoundException("Couldnt find airline"))
}