Scala: Rückkehr hat ihren Platz

Verweise:
Scala-Return-Keyword
Fehlerbehandlung in Scala-Controllern

EDIT3
Dies ist die "endgültige" Lösung, wiederum dank 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)
}

Dann die pimp'd Either-Projektmethode, die irgendwo in Ihrer Anwendung platziert ist (in meinem Fall eine implizite Eigenschaft, die sbt root & child project (sbt root & child project (sbt child project)), deren Basispaketobjekt erweitert um:

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, sind von eingebetteten Return-Statements zu diesem kleinen weißen Zwerg der Dichte übergegangen (ein dickes Lob an @DanBurton, den Haskell-Schlingel ;-))

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)
  ...
}

Ich habe Dans onLeft Either-Projektion als Zuhälter zu Either hinzugefügt, und zwar mit der oben beschriebenen "project" -Methode, die eine Rechtsverzerrung zulässteitherResult project(left-outcome). Grundsätzlich erhalten Sie einen Fail-First-Fehler als Links und einen Erfolg als Rechts, was beim Füttern von Optionsergebnissen zum Verständnis nicht funktioniert (Sie erhalten nur das Ergebnis Some / None).

Das Einzige, wovon ich nicht begeistert bin, ist die Angabe des Typs für dasproject(Conflict(param)); Ich dachte, der Compiler wäre in der Lage, den linken Bedingungstyp aus dem übergebenen entweder abzuleiten: anscheinend nicht.

Auf jeden Fall ist klar, dass der funktionale Ansatz die Notwendigkeit eingebetteter Return-Anweisungen überflüssig macht, wie ich es mit dem If / else-Imperativ-Ansatz versucht habe.

BEARBEITEN
Das funktionale Äquivalent ist:

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

Das ist sicherlich ein dicht gepackter Codeblock, in dem viel passiert. Ich würde jedoch argumentieren, dass entsprechender Imperativ-Code mit eingebetteten Rückgaben nicht nur ähnlich prägnant, sondern auch einfacher zu finden ist (mit dem zusätzlichen Vorteil, dass weniger nachgestellte Curlies und Parens nachverfolgt werden können).

ORIGINAL
Mich in einer zwingenden Situation wiederfinden; möchte einen alternativen Ansatz für Folgendes sehen (der aufgrund der Verwendung des Schlüsselworts return und des Fehlens eines expliziten Typs für die Methode nicht funktioniert):

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 diesem Fall mag ich die Verwendung von return, da Sie vermeiden, mehrere if / else-Blöcke, Folds oder Matches zu verschachteln oder den nicht-imperativen Ansatz auszufüllen. Das Problem ist natürlich, dass es nicht funktioniert. Es muss ein expliziter Rückgabetyp angegeben werden, der seine eigenen Probleme hat, da ich noch nicht herausgefunden habe, wie ein Typ angegeben werden kann, der alle Anforderungen erfüllt, die an Play magic gestellt werden - nein,def save: Result, funktioniert nicht, da der Compiler sich dann beschwertimplicit result jetzt ohne expliziten Typ ;-(

In jedem Fall bieten Beispiele für Play-Frameworks die Bedingung la, la, la, la happy 1-shot-deal fold (Fehler, Erfolg), was in der realen Welt nicht immer der Fall ist ;-)

Was ist das idiomatische Äquivalent (ohne Verwendung von Return) zum obigen Codeblock? Ich nehme an, es wäre geschachtelt, wenn / else, match oder fold, was bei jeder geschachtelten Bedingung ein bisschen hässlich wird und einrückt.

Antworten auf die Frage(4)

Ihre Antwort auf die Frage