Scala: retorno tem seu lugar

Referências:
Palavra-chave de retorno de Scala
manipulação de erros em controladores de scala

EDIT3
Esta é a solução "final", novamente graças a 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)
}

Em seguida, o pimp'd Either método de projeto, colocado em algum lugar em seu aplicativo (no meu caso, um traço implícito que sbt root & child project (s) estende seu objeto de pacote base de:

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
Evolução, passaram de declarações de retorno embutidas para essa pequena anã branca de densidade (parabéns para @DanBurton, o malandro 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)
  ...
}

Eu adicionei a projeção OnLeft de Dan como um proxeneta para o Either, com o método "project" acima, que permite o uso correto do lado direito.eitherResult project(left-outcome). Basicamente, você obtém o primeiro erro como uma esquerda e o sucesso como um direito, algo que não funcionaria ao alimentar os resultados da Opção para a compreensão (você obtém apenas o resultado Algum / Nenhum).

A única coisa que eu não estou muito feliz é ter que especificar o tipo para oproject(Conflict(param)); Eu pensei que o compilador seria capaz de inferir o tipo de condição à esquerda do que está sendo passado para ele: aparentemente não.

De qualquer forma, está claro que a abordagem funcional elimina a necessidade de declarações de retorno embutidas, como eu tentava fazer com a abordagem imperativa if / else.

EDITAR
O equivalente funcional é:

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

que é certamente um bloco de código densamente cheio de energia, muita coisa acontecendo lá; no entanto, eu diria que o código imperativo correspondente com retornos incorporados não é apenas similarmente conciso, mas também mais fácil de grok (com o benefício adicional de menos curlies finais e parentes para acompanhar)

ORIGINAL
Encontrando-me em uma situação imperativa; gostaria de ver uma abordagem alternativa para o seguinte (que não funciona devido ao uso de palavra-chave de retorno e falta de tipo explícito no método):

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

Nesse caso, eu gosto do uso de retorno, pois você evita aninhar vários blocos if / else, dobras ou correspondências, ou preencher a abordagem não imperativa em branco. O problema, é claro, é que ele não funciona, um tipo de retorno explícito tem que ser especificado, o qual tem seus próprios problemas, pois ainda tenho que descobrir como especificar um tipo que satisfaça qualquer mágica do Play - não,def save: Result, não funciona como o compilador, em seguida, reclamaimplicit result agora não tendo um tipo explícito ;-(

De qualquer forma, os exemplos de framework do Play fornecem la, la, la, la feliz condição de dobra (erro, sucesso) que nem sempre é o caso no mundo real ;-)

Então, qual é o equivalente idiomático (sem uso de retorno) ao bloco de código acima? Eu suponho que seria aninhado if / else, match ou fold, o que fica um pouco feio, recuando com cada condição aninhada.

questionAnswers(4)

yourAnswerToTheQuestion