13

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.

Community
  • 1
  • 1
virtualeyes
  • 11,147
  • 6
  • 56
  • 91
  • I wouldn't say it's "idiomatic" to avoid return, especially in obviously imperative cases like this where various effects (`generateOrderNum`, `dao.createMember`, etc) depend on whether or not certain conditions hold. – Dan Burton Jun 01 '12 at 23:11
  • @DanBurton exactly, thus, return has its place in Scala, just not without hassle in some cases – virtualeyes Jun 02 '12 at 07:00
  • Despite what I said, I decided to devise a monadic solution that uses `Left` values to short-circuit computation instead of `return`. I think it turned out quite well, though I'm not comfortable enough with Scala to be able to declare it a 100% sound solution. (I'm quite confident of the Haskell portion.) I'm not even sure where (or if at all) in the Scala standard libraries a suitable monad instance for `Either` can be found. – Dan Burton Jun 02 '12 at 22:32
  • @DanBurton if you look at my updated question you'll see the functional equivalent of my original imperative example; the folds occur over Either, part of the scala standard library. With persistence operations that span multiple tables I've coupled Scala's Option type with for comprehensions to perform safe, transactional queries. Going functional application-wide is a process, however, something that this question helped me move further toward (and further away from imperative roots) – virtualeyes Jun 02 '12 at 22:52
  • 1
    I suggest you create a question from your title. – Somatik Jun 03 '12 at 15:21
  • @virtualeyes: I must be missing something, but why not make `EitherProvidesProjection` an `implicit class` instead of `class` + the `implicit def`? So that you could mark it as `@inline`? – Erik Kaplun Feb 09 '14 at 01:20
  • @ErikAllik you're missing when this was posted, during Scala 2.9 days ;-) As of 2.10 it is indeed an implicit class, and has evolved a bit since then. Would be nice if Either were biased by default, but the implicit does the trick. – virtualeyes Feb 09 '14 at 08:28
  • @virtualeyes: ah, right; I "joined" Scala at 2.10.x, so I wouldn't know these things—apologies :) – Erik Kaplun Feb 09 '14 at 12:25
  • @virtualeyes Could you remove the edits and put them in an answer? It's perfectly acceptable SE practice to do that. As it is, the question is practically unreadable. – itsbruce Nov 22 '17 at 10:20

4 Answers4

28

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):

bindFormRequest :: Request -> Form -> BoundForm
hasErrors :: BoundForm -> Bool

processPayment :: Model -> OrderNum -> TransID
isEmpty :: TransID -> Bool

Let's pause here. At this point, I'm sort of cringing a bit at boundFormHasErrors and transIDisEmpty. Both of these things imply that the possibility of failure is injected into BoundForm and TransID respectively. That's bad. Instead, the possibility of failure should be maintained separate. Allow me to propose this alternative:

bindFormRequest :: Request -> Form -> Either FormBindError BoundForm
processPayment :: Model -> OrderNum -> Either TransError TransID 

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.

save :: Request -> IO Action

form :: Form
withForm :: BoundForm -> Action

getModel :: BoundForm -> Model
generateOrderNum :: IO OrderNum
withGlobalError :: ... -> BoundForm -> BoundForm

getHeader :: String -> Request -> String
dao :: DAO
createMember :: Model -> String -> OrderNum -> TransID
             -> DAO -> IO (Either DAOErr (Foo, Bar, Baz))

allGood :: Foo -> Bar -> Baz -> IO Action

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 an Action, and so stopping with a FormBindError 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 a Left.

-- if we have an `Either a a', then we can always get an `a' out of it!
unEither :: Either a a -> a
unEither (Left a) = a
unEither (Right a) = a

onLeft :: Either l r -> (l -> l') -> Either l' r
(Left l)  `onLeft` f = Left (f l)
(Right r) `onLeft` _ = Right r

At this point, in Haskell, I would talk about monad transformers, and stacking EitherT on top of IO. However, in Scala, this is not a concern, so wherever we see IO Foo, we can just pretend it is a Foo.

Alright, let's write save. We will use do syntax, and later will translate it to Scala's for syntax. Recall in for syntax you are allowed to do three things:

  • assign from a generator using <- (this is comparable to Haskell's <-)
  • assign a name to the result of a computation using = (this is comparable to Haskell's let)
  • use a filter with the keyword if (this is comparable to Haskell's guard 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 as return in Haskell. We will restrict ourselves to these things to make sure that the translation from Haskell to Scala is smooth.

save :: Request -> Action
save request = unEither $ do
  bound <- bindFormRequest request form
           `onLeft` (\err -> withForm (getSomeForm err))

  let model = getModel bound
  let orderNum = generateOrderNum
  transID <- processPayment model orderNum
             `onLeft` (\err -> withForm (withGlobalError ... bound))

  let ip = getHeader "X-Forwarded-For" request
  (foo, bar, baz) <- createMember model ip orderNum transID dao
                     `onLeft` (\err -> withForm (withGlobalError ... bound))

  return $ allGood foo bar baz

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/69442

OK, so now let's translate that to Scala. First, the Either helpers.

Here begins the Scala

// be careful how you use this.
// Scala's subtyping can really screw with you if you don't know what you're doing
def unEither[A](e: Either[A, A]): A = e match {
  case Left(a)  => a
  case Right(a) => a
}

def onLeft[L1, L2, R](e: Either[L1, R], f: L1 => L2) = e match {
  case Left(l) = Left(f(l))
  case Right(r) = Right(r)
}

Now, the save method

def save = Action { implicit request => unEither( for {
  bound <- onLeft(form.bindFormRequest,
                  err => Ok(withForm(err.getSomeForm))).right

  model = bound.get
  orderNum = generateOrderNum()
  transID <- onLeft(processPayment(model, orderNum),
                    err => Ok(withForm(bound.withGlobalError(...))).right

  ip = request.headers.get("X-Forwarded-For")
  (foo, bar, baz) <- onLeft(dao.createMember(model, ip, orderNum, transID),
                            err => Ok(withForm(bound.withGlobalError(...))).right
} yield allGood(foo, bar, baz) ) }

Note that variables on the left hand side of <- or = are implicitly considered to be vals since they are inside of a for block. You should feel free to change onLeft so that it is pimped onto Either values for prettier usage. Also, make sure you import an appropriate "Monad instance" for Eithers.

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" Eithers to make them work with for syntax. This is done by adding .right to the Either values on the right-hand side of the <-. No extra imports necessary. This could be done inside of onLeft for prettier-looking code. See also: https://stackoverflow.com/a/10866844/208257 ]

Community
  • 1
  • 1
Dan Burton
  • 53,238
  • 27
  • 117
  • 198
  • 1
    +10, I learned me some Haskell (did not know Scala functional style read so similarly), and you went beyond the call of duty = checkmark goes here – virtualeyes Jun 02 '12 at 23:02
  • btw, I like how you wrapped the computation in a for comprehension; I had considered that but assumed, somewhat blindly, that any failure would result in a None outcome, which you have shown to not be the case via return fail-or-success for {} conditions, cool stuff – virtualeyes Jun 02 '12 at 23:06
5

What about some nested defs?

def save = Action { implicit request =>
  def transID = {
    val model = bound.get
    val orderNum = generateOrderNum()
    processPayment(model, orderNum)
  }
  def result = {
    val ip = request.headers.get("X-Forwarded-For")
    dao.createMember(model, ip, orderNum, transID)
  }
  val bound = form.bindFromRequest

  if(bound.hasErrors) Ok(withForm(bound))
  else if(transID.isEmpty) Ok(withForm( bound.withGlobalError(...) ))
  else result match {
    case Left(_) => 
      Ok(withForm( bound.withGlobalError(...) ))
    case Right((foo, bar, baz)) =>
      // all good: generate pdf, email, redirect with success msg
    }
  }
}
kiritsuku
  • 52,967
  • 18
  • 114
  • 136
  • Interesting. Definitely a good choice for a team that understands Scala well, but I'd be wary of asking Java-programmers-just-learning-Scala to maintain this code, because they probably won't understand how `def`s work. On an unrelated note, I'd imagine that the "all good" code could be yet another def, if desired. – Dan Burton Jun 01 '12 at 23:14
  • @Antoras, did not occur to me to take the inner def route, +1. Should point out that the return-less nested if/else code that I am actually working with is not unbearable, just irks me a bit to uglify code with indentation for each conditional – virtualeyes Jun 02 '12 at 07:03
  • 4
    @DanBurton: I don't think that it is very complicated to understand methods in methods. – kiritsuku Jun 02 '12 at 22:19
  • @Antoras I suppose what I meant to say is that they simply might not *know*, and make false assumptions accordingly. I myself am not too familiar with Scala. After thinking about this code, isn't it computing the `transID` block twice? That would be bad, because it would `generateOrderNum` and `processPayment` twice. Perhaps it should be a `lazy val` instead? – Dan Burton Jun 02 '12 at 22:42
  • @DanBurton: Yeah, that's true. I hadn't seen that. My fault. Some implicit knowledge here, to differ between `def`, `val`, `var` and `lazy val`, that's true... – kiritsuku Jun 02 '12 at 22:51
2

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:

def save = Action { implicit request =>
  def result(): Foo = {
    /* All your logic goes in here, including returns */
  }
  result()
}

or, if you prefer, you can use your own data-passing throwable class (without stack trace):

import scala.util.control.ControlThrowable
case class Return[A](val value: A) extends ControlThrowable {}

def save = Action { implicit request =>
  try {
    /* Logic */
    if (exitEarly) throw Return(Ok(blahBlah))
    /* More logic */
  }
  catch {
    case Return(x: Foo) => x
  }
}

Or you could get a little fancier and add your own exception handling:

case class Return[A](val value: A) extends ControlThrowable {}
class ReturnFactory[A]{ def apply(a: A) = throw new Return(a) }
def returning[A: ClassManifest](f: ReturnFactory[A] => A) = {
  try { f(new ReturnFactory[A]) } catch {
    case r: Return[_] =>
      if (implicitly[ClassManifest[A]].erasure.isAssignableFrom(r.value.getClass)) {
        r.value.asInstanceOf[A]
      } else {
        throw new IllegalArgumentException("Wrong Return type")
      }
  } 
}

(If you want to be able to nest the returnings, just rethrow the Return instead of throwing an IllegalArgumentException when the type doesn't match.) You can use this like so:

def bar(i: Int) = returning[String] { ret =>
  if (i<0) ret("fish")
  val j = i*4
  if (j>=20) ret("dish")
  "wish"*j
}

bar(-3)   // "fish"
bar(2)    // "wishwishwishwishwishwishwishwish"
bar(5)    // "dish"

or in your particular case

def save = Action{ implicit request => returning[Foo] { ret =>
  /* Logic goes here, using ret(foo) as needed */
}}

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 in scala.util.control.Breaks which uses something very much like this strategy.)

Rex Kerr
  • 166,841
  • 26
  • 322
  • 407
  • +1 wow, Rex...get some sleep ;-) Interesting approach. I've actually pimped util.Control into "catching ( operation ) option" or "catching ( operation ) either" with logging for various exception types. I'm shooting for concision here, which embedded return does quite nicely; failing that, breaking out payment into a separate handler (as I've done already with membership DAO) is probably the most direct approach toward that end. Thanks for "implicit request => returning[Foo] { ret =>", a nice workaround – virtualeyes Jun 02 '12 at 07:16
  • Does the first block actually work? The bit that says, `/* All your logic goes in here, including returns */`... I just tried it and it seems that explicit `returns` inside named local functions just return from that function, not from the enclosing function, unlike lambdas. Am I missing something? Something changed? – okonomichiyaki Nov 15 '13 at 17:54
  • @spacemanaki - There is only _one_ named local function in that example, so returning from it does exactly the same thing as the later examples do. – Rex Kerr Nov 15 '13 at 17:57
1

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.

def save = Action {implicit request =>
  val bound = form.bindFromRequest
  if (!bound.hasErrors) {
    val model = bound.get
    val ip = request.headers.get("X-Forwarded-For")

    val result = paymentService.processPayment(model, ip)

    result match {
      case Left(_) => Ok(withForm(bound.withGlobalError(...)))
      case Right((foo, bar, baz)) => // all good: generate pdf, email, redirect with success msg
    }
  }
  else Ok(withForm(bound))
}

class PaymentService {
  def processPayment(model, ip): Either[Blah, Blah] = {
    val orderNum = generateOrderNum()
    val transID = processPayment(model, orderNum)
    if (transID.isEmpty) Left(yadda)
    else Right(dao.createMember(model, ip, orderNum, transID))
  }
}

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?

7zark7
  • 10,015
  • 5
  • 39
  • 54
  • 1
    You can write `Either.cond(transID.isEmpty, dao.createMember(model, ip, orderNum, transID), yadda)`, which looks slightly better IMHO. – Landei Jun 02 '12 at 05:49
  • @7zark7 right, that's the other option, breaking code out into separate handlers. Probably a good call, this is a member signup form which of course will also require renewals, which means the payment code block has no business being embedded directly in signup.save(). At any rate, this question is also about the return keyword itself, which, looking at the alternative responses, works quite well in imperative code – virtualeyes Jun 02 '12 at 07:07
  • @7zark7, to get around if/else bound.hasErrors, you can fold (failresult, successResultWithblock {...} ), but have to imagine the fold with embedded anonymous block would make for some very interesting bytecode ;-) – virtualeyes Jun 02 '12 at 07:55
  • doesn't really work, btw, member creation should not exist within your payment service; what about member renewal? At any rate, both Scala & Play rock, but you have to deal with edge case limitations like this, which forces you to actually think, something I try to avoid when possible – virtualeyes Jun 02 '12 at 08:05
  • Maybe I'm being pedantic - but independent of the framework, I steer if/else type logic away from the web layer. E.g. what if you get fed up with Play and want to move to something else like Scalatra? The biz logic is then reusable in this case :-) – 7zark7 Jun 02 '12 at 17:30