Подтвердить что ты не робот

Стиль scala - как избежать большого количества вложенной карты

Очень часто я заканчиваю большим количеством вложенных .map и .getOrElse при проверке нескольких условий для участников.

например:

def save() = CORSAction { request =>
  request.body.asJson.map { json =>
    json.asOpt[Feature].map { feature =>
      MaxEntitiyValidator.checkMaxEntitiesFeature(feature).map { rs =>
        feature.save.map { feature => 
          Ok(toJson(feature.update).toString)
        }.getOrElse {
          BadRequest(toJson(
            Error(status = BAD_REQUEST, message = "Error creating feature entity")
          ))
        }
      }.getOrElse {
        BadRequest(toJson(
          Error(status = BAD_REQUEST, message = "You have already reached the limit of feature.")
        )) 
      }
    }.getOrElse {
      BadRequest(toJson(
        Error(status = BAD_REQUEST, message = "Invalid feature entity")
      )) 
    }
  }.getOrElse {
    BadRequest(toJson(
      Error(status = BAD_REQUEST, message = "Expecting JSON data")
    )) 
  }
}

Вы получаете идею

Я просто хотел узнать, есть ли какой-нибудь идиоматический способ, чтобы держать его более ясным.

4b9b3361

Ответ 1

Если вам не пришлось возвращать другое сообщение для случая "Нет", это было бы идеальным вариантом использования для для понимания. В вашем случае вы, вероятно, захотите использовать монадию проверки, как та, которую вы можете найти в Scalaz. Пример (http://scalaz.github.com/scalaz/scalaz-2.9.0-1-6.0/doc.sxr/scalaz/Validation.scala.html).

В функциональном программировании вы не должны генерировать исключения, но пусть функции, которые могут сбой, возвращают Либо [A, B], где по соглашению A - это тип результата в случае сбоя, а B - тип результата в случае успеха. Затем вы можете совместить с Left (a) или Right (b), чтобы обрабатывать два случая.

Вы можете думать о монаде проверки как расширенном Либо [A, B], где применение последующих функций к валидации приведет либо к результату, либо к первому сбою в цепочке выполнения.

sealed trait Validation[+E, +A] {
  import Scalaz._

  def map[B](f: A => B): Validation[E, B] = this match {
    case Success(a) => Success(f(a))
    case Failure(e) => Failure(e)
  }

  def foreach[U](f: A => U): Unit = this match {
    case Success(a) => f(a)
    case Failure(e) =>
  }

  def flatMap[EE >: E, B](f: A => Validation[EE, B]): Validation[EE, B] = this match {
    case Success(a) => f(a)
    case Failure(e) => Failure(e)
  }

  def either : Either[E, A] = this match {
    case Success(a) => Right(a)
    case Failure(e) => Left(e)
  }

  def isSuccess : Boolean = this match {
    case Success(_) => true
    case Failure(_) => false
  }

  def isFailure : Boolean = !isSuccess

  def toOption : Option[A] = this match {
    case Success(a) => Some(a)
    case Failure(_) => None
  }


}

final case class Success[E, A](a: A) extends Validation[E, A]
final case class Failure[E, A](e: E) extends Validation[E, A]

Теперь ваш код может быть реорганизован с помощью монады проверки в три уровня проверки. Вы должны в основном заменить свою карту на валидацию следующим образом:

def jsonValidation(request:Request):Validation[BadRequest,String] = request.asJson match {
   case None => Failure(BadRequest(toJson(
      Error(status = BAD_REQUEST, message = "Expecting JSON data")
    )
   case Some(data) => Success(data)
}

def featureValidation(validatedJson:Validation[BadRequest,String]): Validation[BadRequest,Feature] = {
validatedJson.flatMap {
  json=> json.asOpt[Feature] match {
    case Some(feature)=> Success(feature)
    case None => Failure( BadRequest(toJson(
      Error(status = BAD_REQUEST, message = "Invalid feature entity")
        )))
  }
}

}

И затем вы связываете их следующим образом: featureValidation(jsonValidation(request))

Ответ 2

Это классический пример того, где использование монады может очистить ваш код. Например, вы можете использовать Lift Box, который никак не привязан к Lift. Тогда ваш код будет выглядеть примерно так:

requestBox.flatMap(asJSON).flatMap(asFeature).flatMap(doSomethingWithFeature)

где asJson - это функция от запроса к Box[JSON] и asFeature - это функция от Feature до некоторого другого Box. Ящик может содержать либо значение, и в этом случае flatMap вызывает функцию с этим значением, или может быть экземпляром Failure, и в этом случае flatMap не вызывает переданную ему функцию.

Если вы опубликовали код кода, который компилируется, я мог бы опубликовать ответ, который компилируется.

Ответ 3

Я попробовал это, чтобы увидеть, может ли шаблонное сопоставление каким-либо образом адаптировать представленный образец кода (в стиле, если не буквально) к чему-то более согласованному.

object MyClass {

  case class Result(val datum: String)
  case class Ok(val _datum: String) extends Result(_datum)
  case class BadRequest(_datum: String) extends Result(_datum)

  case class A {}
  case class B(val a: Option[A])
  case class C(val b: Option[B])
  case class D(val c: Option[C])

  def matcher(op: Option[D]) = {
    (op,
     op.getOrElse(D(None)).c,
     op.getOrElse(D(None)).c.getOrElse(C(None)).b,
     op.getOrElse(D(None)).c.getOrElse(C(None)).b.getOrElse(B(None)).a
    ) match {
      case (Some(d), Some(c), Some(b), Some(a)) => Ok("Woo Hoo!")
      case (Some(d), Some(c), Some(b), None)    => BadRequest("Missing A")
      case (Some(d), Some(c), None,    None)    => BadRequest("Missing B")
      case (Some(d), None,    None,    None)    => BadRequest("Missing C")
      case (None,    None,    None,    None)    => BadRequest("Missing D")
      case _                                    => BadRequest("Egads")
    }
  }
}

Ясно, что есть способы написать это более оптимально; это остается как упражнение для читателя.