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.