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.
What about some nested
defs
?So as a Haskeller, obviously in my mind, the solution to everything is Monads. Step with me for a moment into a simplified world (simplified for me, that is) where your problem is in Haskell, and you have the following types to deal with (as a Haskeller, I sort of have this fetish for types):
Let's pause here. At this point, I'm sort of cringing a bit at
boundFormHasErrors
andtransIDisEmpty
. Both of these things imply that the possibility of failure is injected intoBoundForm
andTransID
respectively. That's bad. Instead, the possibility of failure should be maintained separate. Allow me to propose this alternative:That feels a bit better, and these Eithers are leading into the use of the Either monad. Let's write up some more types though. I'm going to ignore
OK
because that is wrapped around pretty much everything; I'm fudging a little bit but the concepts will still translate just the same. Trust me; I'm bringing this back around to Scala in the end.OK, now I'm going to do something a bit wonky, and let me tell you why. The Either monad works like this: as soon as you hit a
Left
, you stop. (Is it any surprise I chose this monad to emulate early returns?) This is all well and good, but we want to always stop with anAction
, and so stopping with aFormBindError
isn't going to cut it. So let's define two functions that will let us deal with Eithers in such a way that we can install a little more "handling" if we discover aLeft
.At this point, in Haskell, I would talk about monad transformers, and stacking
EitherT
on top ofIO
. However, in Scala, this is not a concern, so wherever we seeIO Foo
, we can just pretend it is aFoo
.Alright, let's write
save
. We will usedo
syntax, and later will translate it toScala
'sfor
syntax. Recall infor
syntax you are allowed to do three things:<-
(this is comparable to Haskell's<-
)=
(this is comparable to Haskell'slet
)if
(this is comparable to Haskell'sguard
function, but we won't use this because it doesn't give us control of the "exceptional" value produced)And then at the end we can
yield
, which is the same asreturn
in Haskell. We will restrict ourselves to these things to make sure that the translation from Haskell to Scala is smooth.Notice something? It looks almost identical to the code you wrote in imperative style!
You may be wondering why I went through all this effort to write up an answer in Haskell. Well, it's because I like to typecheck my answers, and I'm rather familiar with how to do this in Haskell. Here's a file that typechecks, and has all of the type signatures I just specified (sans
IO
): http://hpaste.org/69442OK, so now let's translate that to Scala. First, the
Either
helpers.Here begins the Scala
Now, the
save
methodNote that variables on the left hand side of
<-
or=
are implicitly considered to beval
s since they are inside of afor
block. You should feel free to changeonLeft
so that it is pimped ontoEither
values for prettier usage.Also, make sure you import an appropriate "Monad instance" forEither
s.In conclusion, I just wanted to point out that the whole purpose of monadic sugar is to flatten out nested functional code. So use it!
[edit: in Scala, you have to "right bias"
Either
s to make them work withfor
syntax. This is done by adding.right
to theEither
values on the right-hand side of the<-
. No extra imports necessary. This could be done inside ofonLeft
for prettier-looking code. See also: https://stackoverflow.com/a/10866844/208257 ]Scala internally uses the throw/catch mechanism to handle returns in places where returns are syntactically okay but it actually has to jump out of several methods. So you can either let it do this:
or, if you prefer, you can use your own data-passing throwable class (without stack trace):
Or you could get a little fancier and add your own exception handling:
(If you want to be able to nest the
returning
s, just rethrow theReturn
instead of throwing anIllegalArgumentException
when the type doesn't match.) You can use this like so:or in your particular case
It's not built in, but it shouldn't be terribly hard to explain to people how to use this even if it's not so easy to understand how the capability is built. (Note: Scala does have built in
break
capability inscala.util.control.Breaks
which uses something very much like this strategy.)IMHO, seems the problem here is that you are executing business logic in a controller, and Play signatures don't ahem play nice with return values like this is secondary.
I'd recommend you incapsulate the generateOrderNum, processPayment, createMember calls behind a facade, and that return value can return the appropriate state of the business transaction, which can then be used to return the proper controller state.
Will update this answer with an example in a bit.
Edit: This is pretty sloppy so double-check the syntax, but the gist of my answer is to move your business logic sequence into an external class which will leverage the Either/Left/Right you are already using, but now includes your check for empty Transaction ID in the Left response.
The only thing a little hokey here is the if/else for bound.hasErrors, but not sure of a clean way to fold that into the match.
Make sense?