Ссылки:
Ключевое слово возврата Scala
обработка ошибок в контроллерах scala
РЕДАКТИРОВАТЬ3
Это «окончательное» решение, опять же благодаря Дэну Бертону.
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)
}
Затем метод проекта pimp'd Does, размещенный где-то в вашем приложении (, в моем случае неявная черта, которая sbt root & child project (s )расширяет их базовый объект пакета из:
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)
РЕДАКТИРОВАТЬ2
Эволюция прошла путь от встроенных операторов возврата к этому маленькому белому карлику плотности (слава @DanBurton, мошеннику из 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)
...
}
Я добавил проекцию Дэна onLeft либо в качестве сутенера к Либо с помощью описанного выше метода «проекта», который допускает смещение вправо -eitherResult project(left-outcome)
. По сути, вы получаете неудачу -первую ошибку в качестве левого и успех в качестве правого,что-то, что не будет работать при подаче результатов Option для понимания (, вы получите только результат Some/None ).
Единственное, что меня не впечатляет, это необходимость указывать тип для project(Conflict(param))
; Я думал, что компилятор сможет вывести левый тип условия из переданного ему Либо :, по-видимому, нет.
Во всяком случае, ясно, что функциональный подход устраняет необходимость во встроенных операторах возврата, как я пытался сделать с императивным подходом if/else.
РЕДАКТИРОВАТЬ
Функциональный эквивалент::
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
)}
)}
)
который, безусловно, является плотно упакованным блоком кода, в котором много чего происходит; тем не менее, я бы сказал, что соответствующий императивный код со встроенными возвратами не только столь же лаконичен, но и легче для понимания (с дополнительным преимуществом меньшего количества замыкающих фигурных скобок для отслеживания)
ОРИГИНАЛ
Оказаться в императивной ситуации; хотел бы увидеть альтернативный подход к следующему (, который не работает из-за использования ключевого слова return и отсутствия явного типа в методе):
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
}
}
}
В этом случае мне нравится использование возврата, так как вы избегаете вложения нескольких блоков if/else, сгибов, совпадений или заполнения -в -пустого -не императивного -подхода. Проблема, конечно, в том, что это не работает, нужно указать явный тип возвращаемого значения, что имеет свои проблемы, поскольку мне еще предстоит выяснить, как указать тип, который удовлетворяет любой магии Play, которая работает --нет, def save: Result
, не работает, так как компилятор жалуется на то, что implicit result
теперь не имеет явного типа ;-(
Во всяком случае, примеры фреймворка Play показывают la, la, la, la счастливый 1 -выстрел -сделка фолд (ошибка, успех )условие, которое не всегда имеет место в реальном мире™ ;-)
Итак, что такое идиоматический эквивалент (без использования возврата )к приведенному выше кодовому блоку? Я предполагаю, что он будет вложенным, if/else, match или fold, что становится немного некрасивым,отступ с каждым вложенным условием.