Scala: powrót ma swoje miejsce

Referencje:
Słowo kluczowe Scala return
obsługa błędów w kontrolerach scala

EDIT3
Jest to „ostateczne” rozwiązanie, ponownie dzięki Danowi Burtonowi.

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

Następnie metoda pimp'd Either projektu, umieszczona gdzieś w twojej aplikacji (w moim przypadku, cecha implikuje, że projekt sbt root & child rozszerza ich podstawowy pakiet z:

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
Ewolucja przeszła od osadzonych instrukcji zwrotnych do tego małego białego karła gęstości (kudos do @ DanBurton, łobuz Haskell ;-))

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

Dodałem projekcję Dana onLeft Either jako alfonsa do Either za pomocą powyższej metody „projektu”, która pozwala na spolaryzowanie w prawoeitherResult project(left-outcome). Zasadniczo otrzymujesz błąd fail-first jako lewy, a sukces jako prawy, coś, co nie zadziałałoby, gdy karmienie opcji Option spowodowało zrozumienie (otrzymujesz tylko część wyników / brak).

Jedyną rzeczą, która mnie nie zachwyca, jest określenie typu dlaproject(Conflict(param)); Myślałem, że kompilator będzie w stanie wywnioskować lewy typ warunku z Either, który jest mu przekazywany: najwyraźniej nie.

W każdym razie jasne jest, że podejście funkcjonalne eliminuje potrzebę osadzania instrukcji zwrotnych, tak jak próbowałem to zrobić w przypadku trybu rozkazującego if / else.

EDYTOWAĆ
Odpowiednikiem funkcjonalnym jest:

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

z pewnością jest to gęsto upakowany blok kodu, wiele się tam dzieje; jednak twierdziłbym, że odpowiadający im kod rozkazujący z osadzonymi zwrotami jest nie tylko podobnie zwięzły, ale także łatwiejszy w groku (z dodatkową zaletą mniejszej liczby kończących się wątków i parens do śledzenia)

ORYGINALNY
Znajdowanie się w imperatywnej sytuacji; chciałby zobaczyć alternatywne podejście do następującego (które nie działa z powodu użycia słowa kluczowego return i braku wyraźnego typu na metodzie):

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

W tym przypadku podoba mi się użycie powrotu, ponieważ unikasz zagnieżdżania kilku bloków if / else, fałd lub dopasowań, lub wypełniania pustego, nie imperatywnego podejścia. Problem oczywiście polega na tym, że nie działa, musi zostać określony jawny typ powrotu, który ma swoje własne problemy, ponieważ muszę jeszcze określić, jak określić typ, który spełnia wymagania magii Play - nie,def save: Result, nie działa jak kompilator następnie narzekaimplicit result teraz nie ma wyraźnego typu ;-(

W każdym razie przykłady ram Play zawierają la, la, la, la happy 1-shot-deal fold (błąd, sukces), który nie zawsze ma miejsce w prawdziwym świecie ™ ;-)

Więc jaki jest odpowiednik idiomatyczny (bez użycia powrotu) do powyższego bloku kodu? Zakładam, że byłoby zagnieżdżone jeśli / else, match lub fold, co staje się nieco brzydkie, wcinając przy każdym zagnieżdżonym warunku.

questionAnswers(4)

yourAnswerToTheQuestion