2015-10-05 2 views
2

У меня есть класс case Application, который имеет несколько портов ввода, и каждый порт имеет имя. Затем у меня есть другой класс case, который присваивает значения портам приложения.Scala: строка за строкой проверяют на None или проверяете исключения в конце?

case class Port (id: ObjectId, name: String, PortType: String) 
case class Application (id: ObjectId, ports: List[Port]) 
case class AppRun (appId: ObjectId, assignments: List[Assignment]) 
case class Assignment (portName: String, value: String, valueType: String) 

У меня есть приложения и их порт информацию в базе данных, и я получаю в качестве входных данных AppRun. Мне нужно составить список PortValue типа (ниже) показывает значение, присвоенное каждый порт (и сопоставление выполняется по именам портов):

case class PortValue (portId: ObjectId, value: String) 

Есть несколько вещей, которые могут быть неудачными в течение этого сопоставления: приложение id недействителен, порты не совпадают и т. д. Мне кажется естественным написать простой алгоритм, а затем поймать все исключения, но это похоже на Java-ish. С другой стороны, я не могу придумать аккуратный способ справиться с Option, проверяя их один за другим, что приведет к запутыванию кода.

Вопрос в том, как бы вы решили этот способ Scala?


EDIT: Мне нужно отправить соответствующее сообщение назад, когда такое несоответствие происходит, как «Приложение не найдено», и т.д.

+1

Посмотрите на 2-й и 3-й примеры: http://nerd.kelseyinnis.com/blog/2013/11/12/idiomatic -scala-for-assrehension/ – Kolmar

+0

http://fsharpforfunandprofit.com/rop/ – al32

+0

@Kolmar Спасибо. Фактически я сначала написал его с пониманием, и он был очень аккуратным и маленьким, но мне также нужно обрабатывать случаи ошибок и отправить правильное сообщение об ошибке. – Mahdi

ответ

3

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

  • scala.util.Try[T]. Try является либо Success(result), либо Failure(error: Throwable). Это встроенный класс в Scala, и его легко комбинировать или заменить на scala.concurrent.Future, если возникнет такая необходимость.
  • scala.util.Either[E, T]. Создание Throwable для каждой ошибки может быть не очень эффективным из-за необходимости создания stacktrace. Таким образом, Either полезен, если ошибка может быть простой String или некоторым классом приложения без stacktrace. Соглашение должно состоять из Right(result) или Left(error). Недостатки в том, что не семантично иметь «правильный» средний «успех» и «левый» средний «ошибка», а когда вы используете его для понимания или вызова, например. map метод на нем, вы должны указать, хотите ли вы either.right или either.left.
  • scalaz.\/[E, T] Это то же самое, что и Either, но по умолчанию для map и для понимания находится его правая сторона (\/-). Также scalaz предоставляет очень полезные функции sequence и traverse (см. Код ниже).
  • scalaz.Validation[Errors, T] или scalaz.ValidationNel[E, T]. Добавляет очень полезную функциональность для сбора всех ошибок, но имеет небольшие проблемы при использовании в целях понимания.

Вот некоторые примеры кода для вашей проблемы, используя Try:

import scala.util.{Try, Success, Failure} 

def getApplication(appId: ObjectId): Option[Application] = ??? 

/** Convert Option to Try, using a given failure in case of None */ 
def toTry[T](option: Option[T])(failure: => Throwable): Try[T] = 
    option.fold[Try[T]](Failure(failure))(Success(_)) 

/** Convert a List of Try to a Try of List. 
    * If all tries in the List are Success, the result is Success. 
    * Otherwise the result is the first Failure from the list */ 
def sequence[T](tries: List[Try[T]]): Try[List[T]] = 
    tries.find(_.isFailure) match { 
    case Some(Failure(error)) => Failure(error) 
    case _ => Success(tries.map(_.get)) 
    } 
def traverse[T, R](list: List[T])(f: T => Try[R]): Try[List[R]] = 
    sequence(list map f) 

def portValues(task: AppRun): Try[List[PortValue]] = for { 
    app <- toTry(getApplication(task.appId))(
    new RuntimeException("application not found")) 
    portByName = app.ports.map(p => p.name -> p).toMap 
    ports <- traverse(task.assignments) { assignment => 
    val tryPort = toTry(portByName.get(assignment.portName))(
     new RuntimeException(s"no port named ${assignment.portName}")) 
    tryPort.map(port => PortValue(port.id, assignment.value)) 
    } 
} yield ports 

Некоторые соображения:

  • Предоставленные реализации toTry, sequence и traverse являются приблизительными. Во-первых, я бы определил их в implicit class es, чтобы иметь возможность называть их как обычные методы (например, option.toTry(error), или list.traverse(f)).
  • traverse можно реализовать более эффективно (остановка после обнаружения первой ошибки).
  • этот sequence реализация вернет только первый ошибочный порт.
  • Я предпочитаю API как def getApplication(id: ObjectId): Try[Application], а не Option, потому что вы обычно хотите иметь одну и ту же ошибку в каждой части кода, которая ее вызывает, и может также давать различные ошибки (например, идентификатор не найден или ошибка сети). Если у вас есть def getApplication(id: ObjectId): Application, что может бросить ошибку, вы можете просто обернуть его в Try: for { app <- Try(getApplication(id)) ...
+0

Спасибо Kolmar. Это действительно похоже на то, что мне нужно. У моей реальной проблемы есть немного больше материала, чтобы соответствовать и читать из базы данных, но теперь у меня есть хорошая идея, как двигаться вперед. PS: Я использую Salat для чтения из MongoDB, и он возвращает 'Option'. Но ваш 'toTry' должен сделать трюк. – Mahdi

+0

Почему вы создаете карту 'portByName', а не просто используете' find'? – Mahdi

+0

@ Махди Я рад, что это помогло. Этот ответ - лишь некоторый базовый обзор обычных методов для этих проблем. Вы правы, вы можете просто использовать 'find'. Просто на прошлой неделе я исправил очень похожую проблему с производительностью (мне пришлось заменить find на xByName «Карта»), поэтому я оптимизировал преждевременно и в этом ответе. Но в моем случае в списке было сотни элементов, поскольку небольшой список портов 'find' должен, вероятно, быть быстрее. – Kolmar

Смежные вопросы