Scala Future, flatMap, работающая на Either

Есть ли действительно способ преобразовать объект типа Future [Either [Future [T1], Future [T2]]] в и объект типа Either [Future [T1], Future [T2]]?

Может быть, что-то вроде flatMap, которое работает на Either ....

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

case class WebServResp(msg: String)
case class WebStatus(code: Int)
type InnerActionOutType = Either[Future[Option[WebServResp]], Future[WebStatus]]
type InnerActionSig = Future[Option[WebServResp]] => Either[Future[Option[WebServResp]], Future[WebStatus]]

val chainOfActions: InnerActionSig = Seq(
  {prevRespOptFut => 
    println("in action 1: " + prevRespOptFut)
    //dont care about prev result
    Left(Future.successful(Some(WebServResp("result from 1"))))
  },
  {prevRespOptFut => 
    println("in action 2: " + prevFutopt)
    prevRespOptFut.map {prevRespOpt =>
      //i know prevResp contains instance of WebServResp. so i skip the opt-matching
      val prevWebServResp = prevRespOpt.get
      Left(Some(prevWebServResp.msg + " & " + " additional result from 2"))
    }

    //But the outcome of the map above is: Future[Left(...)]
    //What I want is Left(Future[...])
  }
)

type WrappedActionSig = InnerActionOutType => InnerActionOutType 
val wrappedChainOfActions = chainOfActions.map {innerAction => 
  val wrappedAction: WrappedActionSig = {respFromPrevWrappedAction =>
    respFromPrevWrappedAction match {
      case Left(wsRespOptFut) => {        
        innerAction(wsRespOptFut)       
      }
      case Right(wsStatusFut) => {
        respFromPrevWrappedAction
      }
    }
  }
  wrappedAction
}

wrappedChainOfActions.fold(identity[WrappedActionIOType] _)  ((l, r) => l andThen r).apply(Left(None))

ОБНОВЛЕНИЕ ОБНОВЛЕНИЕ ОБНОВЛЕНИЕ

На основе комментариев Дидье ниже (Scala Future, flatMap, который работает на Either) ... вот код, который работает:

//API
case class WebRespString(str: String)
case class WebStatus(code: Int, str: String)
type InnerActionOutType = Either[Future[Option[WebRespString]], Future[WebStatus]]
type InnerActionSig = Future[Option[WebRespString]] => InnerActionOutType

type WrappedActionSig = InnerActionOutType => InnerActionOutType
def executeChainOfActions(chainOfActions: Seq[InnerActionSig]): Future[WebStatus] = {
  val wrappedChainOfActions : Seq[WrappedActionSig] = chainOfActions.map {innerAction => 
    val wrappedAction: WrappedActionSig = {respFromPrevWrappedAction =>
      respFromPrevWrappedAction match {
        case Left(wsRespOptFut) => {        
          innerAction(wsRespOptFut)       }
        case Right(wsStatusFut) => {
          respFromPrevWrappedAction
        }
      }
    }
    wrappedAction
  }  

  val finalResultPossibilities = wrappedChainOfActions.fold(identity[InnerActionOutType] _)  ((l, r) => l andThen r).apply(Left(Future.successful(None)))
  finalResultPossibilities match {
    case Left(webRespStringOptFut) => webRespStringOptFut.map {webRespStringOpt => WebStatus(200, webRespStringOpt.get.str)}
    case Right(webStatusFut) => webStatusFut
  }  
}

//API-USER

executeChainOfActions(Seq(
  {prevRespOptFut => 
    println("in action 1: " + prevRespOptFut)
    //dont care about prev result
    Left(Future.successful(Some(WebRespString("result from 1"))))
  },
  {prevRespOptFut => 
    println("in action 2: " + prevRespOptFut)
    Left(prevRespOptFut.map {prevRespOpt => 
      val prevWebRespString = prevRespOpt.get
      Some(WebRespString(prevWebRespString.str + " & " + " additional result from 2"))
    })
  }  
)).map {webStatus =>
  println(webStatus.code + ":" + webStatus.str)
}

executeChainOfActions(Seq(
  {prevRespOptFut => 
    println("in action 1: " + prevRespOptFut)
    //Let's short-circuit here
    Right(Future.successful(WebStatus(404, "resource non-existent")))
  },
  {prevRespOptFut => 
    println("in action 2: " + prevRespOptFut)
    Left(prevRespOptFut.map {prevRespOpt => 
      val prevWebRespString = prevRespOpt.get
      Some(WebRespString(prevWebRespString.str + " & " + " additional result from 2"))
    })
  }  
)).map {webStatus =>
  println(webStatus.code + ":" + webStatus.str)
}

Спасибо, Рака


person Cokorda Raka    schedule 09.11.2014    source источник
comment
На самом деле я все еще хочу пойти немного дальше, чтобы упростить другим программистам, которые собираются писать внутренние действия ... Итак, InnerActionSig ... вместо Future [Option [WebServResp]] = ›InnerActionOutType .. ., Я хочу, чтобы это было Option [WebServResp] = ›InnerActionOutType ... Но то, как это происходит сейчас, я думаю, достаточно хорошо.   -  person Cokorda Raka    schedule 09.11.2014


Ответы (2)


Тип Future[Either[Future[T1], Future[T2]]] означает, что иногда позже (в будущем) кто-то получает Either, поэтому в это время он будет знать, в каком направлении пойдет расчет и получит ли он еще позже T1 или T2.

Таким образом, информация о том, какая ветвь будет выбрана (Left или Right), появится позже. Тип Either[Future[T1], Future[T2] означает, что у человека есть это знание сейчас (не знаю, каков будет результат, но уже знает, какого типа он будет). Единственный способ выбраться из будущего - это ждать.

Здесь нет никакой магии, единственный способ позже стать настоящим - это ждать, что делается с помощью result в Будущем и не рекомендуется. `

Вместо этого вы можете сказать, что вам не слишком интересно знать, какая ветвь взята, пока она не завершена, так что Future[Either[T1, T2]] достаточно хорошо. Это просто. Допустим, у вас есть Either, и вы бы предпочли не смотреть, а дождаться фактического результата:

def asFuture[T1, T2](
    either: Either[Future[T1], Future[T2]])(
    implicit ec: ExecutionContext)
 : Future[Either[T1, T2] =  either match {
   case Left(ft1) => ft1 map {t1 => Left(t1)}
   case Right(ft2) => ft2 map {t2 => Right(t2)}
}

У вас еще нет Either, но будущее на этом, так что просто flatMap

f.flatMap(asFuture) : Future[Either[T1, T2]]

(потребуется ExecutionContext неявно доступный)

person Didier Dupont    schedule 09.11.2014
comment
Привет, Дидье, я просто предоставил контекст для своего первоначального вопроса. Я полагаю, что название, которое я поставил для вопроса, не соответствует действительности. Что касается вашего комментария, из моего блока я знаю, что мой возврат всегда останется. Это просто содержание того, что осталось в будущем. Сложная часть здесь, вход в функцию, которая вычисляет этот контент, также в будущем ... - person Cokorda Raka; 09.11.2014
comment
Что ж, если вы действительно знаете, что Either - это левый, это немного странно, но тогда выбросьте его в будущее: future map {case Left(l) => l}, и если вы хотите, чтобы это было левое, тогда Left(future map {case Left(l) => l} - person Didier Dupont; 09.11.2014
comment
Хорошо, основываясь на вашем последнем комментарии ... так что я думаю, было бы проще, если бы я переопределил InnerActionOutType с Either [Future [Option [WebServResp]], Future [WebStatus]] на Future [Either [Option [WebServResp], WebStatus]] - person Cokorda Raka; 09.11.2014
comment
Привет, извините, я только что прочитал ваш дополнительный комментарий .... Да, я думаю, что вы правы ... Дайте мне попробовать: stackoverflow.com/questions/26826190/ - person Cokorda Raka; 09.11.2014

Похоже, вам действительно не нужно, чтобы случай "отказа" Either был Future? В этом случае мы можем использовать scalaz (обратите внимание, что случай «успеха» любого из них должен быть справа):

import scalaz._
import scalaz.Scalaz._

def futureEitherFutureToFuture[A, B](f: Future[Either[A, Future[B]]])(
  implicit ec: ExecutionContext): Future[Either[A, B]] =
  f.flatMap(_.sequence)

Но, вероятно, лучше всегда держать Future снаружи в вашем API и flatMap в вашем коде, а не в клиенте. (Вот это часть foldLeftM):

case class WebServResp(msg: String)
case class WebStatus(code: Int)
type OWSR = Option[WebServResp]
type InnerActionOutType = Future[Either[WebStatus, OWSR]]
type InnerActionSig = OWSR => InnerActionOutType

def executeChain(chain: List[InnerActionSig]): InnerActionOutType = 
  chain.foldLeftM(None: OWSR) {
    (prevResp, action) => action(prevResp)
  }

//if you want that same API
def executeChainOfActions(chainOfActions: Seq[InnerActionSig]) =
  executeChain(chainOfActions.toList).map {
    case Left(webStatus) => webStatus
    case Right(webRespStringOpt) => WebStatus(200, webRespStringOpt.get.str)
  }

(Если вам нужны действия типа «восстановление», поэтому вам действительно нужно, чтобы OWSR был Either, тогда вы все равно должны сделать InnerActionOutType Future[Either[...]], и вы можете использовать .traverse или .sequence в своих действиях по мере необходимости. Если у вас есть пример " error-recovery ", я могу привести пример этого здесь)

person lmm    schedule 09.11.2014