Как я могу (лучше всего) преобразовать вариант в попытку?

Как я могу (лучше всего) преобразовать Option, возвращаемый вызовом метода, в Try (по предпочтению, хотя может быть и «Libor», или «scalaz \/», или даже «Validation»), включая указание значения «Отказ», если это уместно?

Например, у меня есть следующий код, который кажется неуклюжим, но, по крайней мере, выполняет (большую часть) работу:

import scala.util._

case class ARef(value: String)
case class BRef(value: String)
case class A(ref: ARef, bRef: BRef)
class MismatchException(msg: String) extends RuntimeException(msg)

trait MyTry {

  // Given:
  val validBRefs: List[BRef]

  // Want to go from an Option[A] (obtained, eg., via a function call passing a provided ARef)
  // to a Try[BRef], where the b-ref needs to be checked against the above list of BRefs or fail:

  def getValidBRefForReferencedA(aRef: ARef): Try[BRef] = {

    val abRef = for {
      a <- get[A](aRef) // Some function that returns an Option[A]
      abRef = a.bRef
      _ <- validBRefs.find(_ == abRef)
    } yield (abRef)

    abRef match {
      case Some(bRef) => Success(bRef)
      case None => Failure(new MismatchException("No B found matching A's B-ref"))
    }
  }
}

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

Кроме того, я бы предпочел иметь возможность указать другое сообщение об ошибке, если вызов для возврата Option[A] из ARef завершился неудачно (возвращено None) по сравнению с ошибкой проверки BRef (мне важно знать только одну причину сбоя, поэтому scalaz Validation не кажется идеальным вариантом).

Это подходящее место для использования преобразователя монад? Если да, то предоставляет ли scalaz подходящий вариант или кто-нибудь может привести пример того, как он будет выглядеть?


person Shadowlands    schedule 08.07.2013    source источник
comment
Вы имеете в виду что-то вроде Try{ abRef.getOrElse(throw new MismatchException("No B found matching A's B-ref")) } или abRef.map{ Success(_) }.getOrElse(Failure(new MismatchException("No B found matching A's B-ref")))?   -  person senia    schedule 08.07.2013
comment
@senia больше последнее: abRef.map{ Success(_) }.getOrElse(Failure(new MismatchException("No B found matching A's B-ref"))), но кажется, что должно быть возможно - и более идиоматично - каким-то образом встроить это в понимание для.   -  person Shadowlands    schedule 08.07.2013
comment
В качестве примечания, почему бы не использовать filter на Option, то есть заменить for-понимание на get[A](aRef).map(_.bRef).filter(validBRefs.contains)?   -  person Travis Brown    schedule 08.07.2013


Ответы (7)


Если вы начнете с Try с самого начала с вашим for-comp, вы можете исключить совпадение в конце. Вы можете сделать это, принудительно переведя Option в Try через fold. Вот как это может выглядеть:

def getValidBRefForReferencedA(aRef: ARef): Try[BRef] = {
  for {
    a <- get[A](aRef).fold[Try[A]](Failure[A](new OtherException("Invalid aRef supplied")))(Success(_))
    abRef = a.bRef
    _ <- validBRefs.find(_ == abRef).fold[Try[BRef]](Failure(new MismatchException("No B found matching A's B-ref")))(Success(_))
  } yield abRef
}

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

person cmbaxter    schedule 08.07.2013
comment
Ах, это то, что я ожидал, должно быть возможно - пропустил метод fold (хотя кажется, что он появился только в Scala 2.10.x). Привет @cmbaxter! - person Shadowlands; 08.07.2013

Вы можете использовать неявное преобразование

  implicit class OptionOps[A](opt: Option[A]) {

    def toTry(msg: String): Try[A] = {
      opt
        .map(Success(_))
        .getOrElse(Failure(new NoSuchElementException(msg)))
    }
  }

Стандартная библиотека Scala использует такой подход. См. http://docs.scala-lang.org/tutorials/FAQ/finding-implicits.html#companion-objects-of-a-type

person JasonG    schedule 10.07.2017

Коротко и просто

Try(option.get)

нет необходимости в причудливом отображении. Если опция пуста, вы получите сообщение об ошибке, например:

java.util.NoSuchElementException: None.get
person Atais    schedule 26.03.2018
comment
За исключением того, что вы полагаетесь на механизм Try, а также на создание исключений вместо простого преобразования значений. Это неэффективно. - person LMeyer; 13.02.2019
comment
@LMeyer: создание Failure(e) (которое создает исключение, но не перехватывает его), вероятно, так же дорого, как создание и перехват исключения. Любой, кто заботится о производительности, должен избегать Try в первую очередь. - person Richard Gomes; 09.06.2019
comment
Я думаю, вы правы. Если вас беспокоит производительность, лучше использовать stackoverflow.com/a/45017589/1549135. - person Atais; 12.08.2019

myOption
  .toRight(new Exception("y"))
  .toTry

Этот код вернет либо Success(x), если myOption равно Some(x), либо Failure(Exception("y")), если None.

person Minduca    schedule 10.11.2020
comment
Это работает отлично и довольно прямолинейно. Именно то, что мне нужно, преобразовать Option[T] в Try[T] и иметь пользовательский сбой[U], когда внутри Option[T] ничего нет. - person Brian Hsu; 24.07.2021

Если вы хотите использовать Either, вы можете использовать Option.toRight:

def getValidBRefForReferencedA(aRef: ARef): Either[Throwable,BRef] = {    
  for {
    a <- get[A](aRef).toRight[Throwable](new Exception("Invalid ARef")).right
    bRef <- validBRefs.find(_ == a.bRef).toRight(new MismatchException("No B found matching A's B-ref")).right
  } yield bRef
}

Используя Try, вы можете просто написать свой код очень процедурным образом, выбрасывая соответствующие исключения там, где это необходимо, и обертывая все Try.apply (который перехватит исключение и представит их как экземпляры Failure).

def getValidBRefForReferencedA(aRef: ARef): Try[BRef] = Try {
  val a = get[A](aRef).getOrElse(throw new Exception("Invalid ARef"))
  validBRefs.find(_ == a.bRef).getOrElse(throw new MismatchException("No B found matching A's B-ref"))
}
person Régis Jean-Gilles    schedule 08.07.2013

[Отредактировано для выявления различных сбоев]

Пытался упростить

def getValidBRefForReferencedA(aRef: ARef): Try[BRef] = {
  val abRef = for {
    a <- get[A](aRef)
    bRef = a.bRef
    result = Either.cond(validBRefs.contains(bRef), bRef, "Invalid B Reference")
  } yield result

  abRef.map {
    case Right(bRef) => Success(bRef)
    case Left(error) => Failure(new InvalidReferenceException(error))
  }.getOrElse(Failure(new MismatchException("No B found matching A's B-ref")))
}
person pagoda_5b    schedule 08.07.2013

Я разработал альтернативное решение, хотя оно по-прежнему не позволяет мне указать другое сообщение об ошибке для случая, когда Option[A] имеет значение None, а BRef недействителен:

def getValidBRefForReferencedA(aRef: ARef): Try[BRef] = 
  Try {
    (for {
      a <- get[A](aRef)
      bRef = a.bRef
      _ <- bs.find(_ == bRef)
    } yield (bRef)) getOrElse (throw new MismatchException("No B found matching A's B-ref"))
  }

Я полагаю, я ожидаю, что должен быть способ быстро преобразовать возвращенный Option[A] в Try (подходящим идиоматичным способом Scala - например, внутри for-comprehension), затем продолжить обработку (получение и проверка b-ref), устанавливая любые соответствующие отказы по пути.

person Shadowlands    schedule 08.07.2013