References:
Scala return keyword
handling errors in scala controllers
EDIT3
This is the "final" solution, again thanks to Dan Burton.
def save = Action { implicit request =>
val(orderNum, ip) = (generateOrderNum, request.remoteAddress)
val result = for {
model <- bindForm(form).right // error condition already json'd
transID <- payment.process(model, orderNum) project json
userID <- dao.create(model, ip, orderNum, transID) project json
} yield (userID, transID)
}
Then the pimp'd Either project method, placed somewhere in your application (in my case, an implicits trait that sbt root & child project(s) extends their base package object from:
class EitherProvidesProjection[L1, R](e: Either[L1, R]) {
def project[L1, L2](f: L1 => L2) = e match {
case Left(l:L1) => Left(f(l)).right
case Right(r) => Right(r).right
}
}
@inline implicit final def either2Projection[L,R](e: Either[L,R]) = new EitherProvidesProjection(e)
EDIT2
Evolution, have gone from embedded return statements to this little white dwarf of density (kudos to @DanBurton, the Haskell rascal ;-))
def save = Action { implicit request =>
val(orderNum, ip) = (generateOrderNum, request.remoteAddress)
val result = for {
model <- form.bindFromRequest fold(Left(_), Right(_)) project( (f:Form) => Conflict(f.errorsAsJson) )
transID <- payment.process(model, orderNum) project(Conflict(_:String))
userID <- dao.create(model, ip, orderNum, transID) project(Conflict(_:String))
} yield (userID, transID)
...
}
I have added Dan's onLeft Either projection as a pimp to Either, with the above "project" method, which allows for right-biased eitherResult project(left-outcome)
. Basically you get fail-first error as a Left and success as a Right, something that would not work when feeding Option outcomes to for comprehension (you only get Some/None outcome).
The only thing I'm not thrilled with is having to specify the type for the project(Conflict(param))
; I thought the compiler would be able to infer the left condition type from the Either that is being passed to it: apparently not.
At any rate, it's clear that the functional approach obviates the need for embedded return statements as I was trying to do with if/else imperative approach.
EDIT
The functional equivalent is:
val bound = form.bindFromRequest
bound fold(
error=> withForm(error),
model=> {
val orderNum = generateOrderNum()
payment.process(model, orderNum) fold (
whyfail=> withForm( bound.withGlobalError(whyfail) ),
transID=> {
val ip = request.headers.get("X-Forwarded-For")
dao.createMember(model, ip, orderNum, transID) fold (
errcode=>
Ok(withForm( bound.withGlobalError(i18n(errcode)) )),
userID=>
// generate pdf, email, redirect with flash success
)}
)}
)
which is certainly a densely power packed block of code, a lot happening there; however, I would argue that corresponding imperative code with embedded returns is not only similarly concise, but also easier to grok (with added benefit of fewer trailing curlies & parens to keep track of)
ORIGINAL
Finding myself in an imperative situation; would like to see an alternative approach to the following (which does not work due to the use of return keyword and lack of explicit type on method):
def save = Action { implicit request =>
val bound = form.bindFromRequest
if(bound.hasErrors) return Ok(withForm(bound))
val model = bound.get
val orderNum = generateOrderNum()
val transID = processPayment(model, orderNum)
if(transID.isEmpty) return Ok(withForm( bound.withGlobalError(...) ))
val ip = request.headers.get("X-Forwarded-For")
val result = dao.createMember(model, ip, orderNum, transID)
result match {
case Left(_) =>
Ok(withForm( bound.withGlobalError(...) ))
case Right((foo, bar, baz)) =>
// all good: generate pdf, email, redirect with success msg
}
}
}
In this case I like the use of return as you avoid nesting several if/else blocks, or folds, or matches, or fill-in-the-blank non-imperative approach. The problem of course, is that it doesn't work, an explicit return type has to specified, which has its own issues as I have yet to figure out how to specify a type that satisfies whatever Play magic is at work -- no, def save: Result
, does not work as the compiler then complains about implicit result
now not having an explicit type ;-(
At any rate, Play framework examples provide la, la, la, la happy 1-shot-deal fold(error, success) condition which is not always the case in the real world™ ;-)
So what is the idiomatic equivalent (without use of return) to above code block? I assume it would be nested if/else, match, or fold, which gets a bit ugly, indenting with each nested condition.
See Question&Answers more detail:
os