Scala :возвращение имеет свое место

Ссылки:
Ключевое слово возврата 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, что становится немного некрасивым,отступ с каждым вложенным условием.

13
задан Community 23 May 2017 в 12:02
поделиться