Зависимости методов и обработка ошибок - PullRequest
2 голосов
/ 09 августа 2009

Я создаю небольшую систему управления проблемами в Lift для изучения Scala и Lift.

У меня есть вид, который отображает одну проблему, относящуюся к проекту. Прежде чем связать данные с шаблоном представления, я хочу проверить, что у меня есть все необходимые данные, поэтому я хочу специально проверить, что:

  • Предоставлен параметр ID проекта
  • Проект существует с предоставленным идентификатором проекта
  • Указан параметр идентификатора проблемы
  • Проблема существует с предоставленным идентификатором проблемы

И их нужно оценивать по порядку, поэтому, если бы я написал это сейчас с моим текущим пониманием Scala, я бы сделал следующее:

class Project {
    def findByID(xhtml:NodeSeq): NodeSeq = 
        param("projectID") match {
            case Full(projectID) =>
                Project.findByID(projectID) match {
                    case Full(projectID) =>
                        param("issueID") match {
                            ....
                        }
                    case _ => Text("Project does not exist")
                }
            case _ => Text("projectIDNotSupplied")
        }
}

Так что мне интересно, есть ли более простой способ сделать это? Я думаю, что для выражения может быть в состоянии выполнить что-то подобное. Имейте в виду, что Project.findByID возвращает Box [Project].

Ответы [ 3 ]

4 голосов
/ 13 августа 2009

Извините, я так опаздываю на шоу, но, как говорит Дэниел, вы действительно можете использовать Lift's Box и? ~, Чтобы сделать что-то подобное. Например:

import net.liftweb.util.Box
import net.liftweb.util.Box._

class Project {
  def findByID(xhtml:NodeSeq): NodeSeq = 
    (for {
      projectID <- param("projectID") ?~ "projectIDNotSupplied"
      project <- Project.findById(projectID) ?~ "Project does not exist"
      issueID <- param("issueID") ?~ "issueIDNotSupplied"
      ...
    } yield {
      ...
    }) match {
      case Full(n) => n
      case Failure(msg, _, _) => Text(msg)
      case _ => Text("fail")
    }
}

Что? ~ Превращает пустой ящик в ящик с ошибкой с данным сообщением об ошибке строки, но ничего не делает с полным (успешным) ящиком. Таким образом, возвращаемое значение findByID будет Full, если все успешно, или Failure (с данным сообщением об ошибке) в противном случае. Если вы хотите, чтобы сбои соединялись, используйте? ~! вместо этого.

1 голос
/ 09 августа 2009

Я не знаю Лифта, поэтому не могу ответить ни на какие вопросы о Лифте. Однако я нашел способ решить одну из ваших проблем - написать последовательность действий «проверяй и работай», не прибегая к сопоставлению вложенных шаблонов.

Основным используемым типом данных здесь является Option, но я уверен, что его будет легко адаптировать к вашим потребностям. Здесь мы хотим выполнить последовательность

  1. проверить состояние
  2. продолжить в случае успеха
  3. завершить и вернуть что-то иначе

Код выполняет своего рода короткое замыкание, когда встречает None, поэтому возвращаемое значение сохраняется при возврате последовательности действий. Для использования начинается с Option, затем пишется «ifSome», если Option является Some, и «ifNone», если Option является None, продолжайте, пока последовательность не будет завершена. Если None встречается в любой точке последовательности, Option, возвращенный параметром call-by-name для isNone, сохраняется и возвращается при вызове окончательного toOption. Используйте «toOption», чтобы получить фактический результат Option.

Посмотрите пример в "main" для некоторых вариантов использования. Удачи!

object Options {

  class RichOption[A](a: Option[A]) {

    def ifSome[B](f: A => Option[B]): RichOption[B] = a match {
      case Some(x) => new RichOption(f(x))
      case None => this.asInstanceOf[RichOption[B]]
    }

    def ifNone[B](f: => Option[B]): RichOption[B] = a match {
      case Some(_) => this.asInstanceOf[RichOption[B]]
      case None => new RichNone(f)
    }

    def toOption[A] = a
  }

  class RichNone[A](a: Option[A]) extends RichOption[A](a) {

    override def ifSome[B](f: A => Option[B]): RichOption[B] = this.asInstanceOf[RichOption[B]]

    override def ifNone[B](f: => Option[B]): RichOption[B] = this.asInstanceOf[RichOption[B]]
  }

  implicit def option2RichOption[A](a: Option[A]): RichOption[A] = new RichOption(a)

  def main(args: Array[String]) {
    println(Some("hello") ifSome(s => Some(s.toUpperCase)) toOption) // prints Some(HELLO)
    println(Some("hello") ifNone(Some("empty")) toOption) // prints Some(hello)
    println(Some("hello") ifSome(s => Some(s.toUpperCase)) ifNone(Some("empty")) toOption) // prints Some(HELLO)
    println(Some("hello") ifNone(Some("empty")) ifSome(s => Some(s.toUpperCase)) toOption) // prints Some(HELLO)
    println((None: Option[String]) ifSome(s => Some(s.toUpperCase)) toOption) // prints None
    println((None: Option[String]) ifNone(Some("empty")) toOption) // prints Some(empty)
    println((None: Option[String]) ifSome(s => Some(s.toUpperCase)) ifNone(Some("empty")) toOption) // prints Some(empty)
    println((None: Option[String]) ifNone(Some("empty")) ifSome(s => Some(s.toUpperCase)) toOption) // prints Some(empty)
    println(Some("hello world") ifSome(s => Some(s.toUpperCase)) ifNone(Some("empty")) ifSome(s => Some(s.length)) ifNone(None) toOption) // prints Some(11)
    println(Some("hello world") ifSome(_ => None) ifNone(Some("goodbye world")) ifSome(s => Some(s.length)) ifNone(None) toOption) // prints Some(goodbye world)
    println((None: Option[String]) ifSome(s => Some(s.toUpperCase)) ifNone(Some("empty")) ifSome(s => Some(s.length)) ifNone(None) toOption) // prints Some(empty)
    println((None: Option[String]) ifSome(_ => None) ifNone(Some("goodbye world")) ifSome(s => Some(s.length)) ifNone(None) toOption) // prints Some(goodbye world)
  }
}
1 голос
/ 09 августа 2009

Я не знаю Lift, но есть несколько вещей, которые я видел в его реализации. Один из них - методы отказа: ?~ и ?~!. Я не уверен точно, как можно их использовать, но это кажется полезным. Другим является open_!, чтобы выбросить исключения.

Теперь карта поддержки Box, flatMap, filter и foreach, так что она может быть полностью использована внутри для понимания:

for(projectID <- param("projectID");
    if Project.findByID(projectID).isDefined;
    issueID <- param("issueID");
    if Issue.findByID(issueID).isDefined)
yield ...

Это не даст вам сообщений об ошибках. Для тех, я думаю, методы, которые я упоминал, или другие, такие как ~>, могли бы дать ответ.

...